This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

library(SingleCellExperiment)
载入需要的程辑包:SummarizedExperiment
载入需要的程辑包:GenomicRanges
载入需要的程辑包:stats4
载入需要的程辑包:BiocGenerics
载入需要的程辑包:parallel

载入程辑包:‘BiocGenerics’

The following objects are masked from ‘package:parallel’:

    clusterApply, clusterApplyLB, clusterCall, clusterEvalQ, clusterExport, clusterMap, parApply,
    parCapply, parLapply, parLapplyLB, parRapply, parSapply, parSapplyLB

The following objects are masked from ‘package:stats’:

    IQR, mad, sd, var, xtabs

The following objects are masked from ‘package:base’:

    anyDuplicated, append, as.data.frame, basename, cbind, colnames, dirname, do.call, duplicated,
    eval, evalq, Filter, Find, get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply,
    match, mget, order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank, rbind, Reduce,
    rownames, sapply, setdiff, sort, table, tapply, union, unique, unsplit, which, which.max,
    which.min

载入需要的程辑包:S4Vectors

载入程辑包:‘S4Vectors’

The following object is masked from ‘package:base’:

    expand.grid

载入需要的程辑包:IRanges
载入需要的程辑包:GenomeInfoDb
载入需要的程辑包:Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with 'browseVignettes()'. To cite Bioconductor,
    see 'citation("Biobase")', and for packages 'citation("pkgname")'.

载入需要的程辑包:DelayedArray
载入需要的程辑包:matrixStats

载入程辑包:‘matrixStats’

The following objects are masked from ‘package:Biobase’:

    anyMissing, rowMedians

载入需要的程辑包:BiocParallel

载入程辑包:‘DelayedArray’

The following objects are masked from ‘package:matrixStats’:

    colMaxs, colMins, colRanges, rowMaxs, rowMins, rowRanges

The following objects are masked from ‘package:base’:

    aperm, apply, rowsum
library(scmap)
Creating a generic function for ‘toJSON’ from package ‘jsonlite’ in package ‘googleVis’
library(Seurat)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

载入程辑包:‘Seurat’

The following object is masked from ‘package:SummarizedExperiment’:

    Assays
library(xgboost)

载入程辑包:‘xgboost’

The following object is masked from ‘package:IRanges’:

    slice
library(Matrix)

载入程辑包:‘Matrix’

The following object is masked from ‘package:S4Vectors’:

    expand
set.seed(1)
source("tianfengRwrappers.R")
载入需要的程辑包:dplyr

载入程辑包:‘dplyr’

The following object is masked from ‘package:xgboost’:

    slice

The following object is masked from ‘package:matrixStats’:

    count

The following object is masked from ‘package:Biobase’:

    combine

The following objects are masked from ‘package:GenomicRanges’:

    intersect, setdiff, union

The following object is masked from ‘package:GenomeInfoDb’:

    intersect

The following objects are masked from ‘package:IRanges’:

    collapse, desc, intersect, setdiff, slice, union

The following objects are masked from ‘package:S4Vectors’:

    first, intersect, rename, setdiff, setequal, union

The following objects are masked from ‘package:BiocGenerics’:

    combine, intersect, setdiff, union

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union

载入需要的程辑包:reticulate
载入需要的程辑包:tidyr

载入程辑包:‘tidyr’

The following objects are masked from ‘package:Matrix’:

    expand, pack, unpack

The following object is masked from ‘package:S4Vectors’:

    expand


载入程辑包:‘MySeuratWrappers’

The following objects are masked from ‘package:Seurat’:

    DimPlot, DoHeatmap, LabelClusters, RidgePlot, VlnPlot


载入程辑包:‘cowplot’

The following object is masked from ‘package:ggpubr’:

    get_legend

载入需要的程辑包:viridisLite

载入程辑包:‘reshape2’

The following object is masked from ‘package:tidyr’:

    smiths

NOTE: Either Arial Narrow or Roboto Condensed fonts are required to use these themes.
      Please use hrbrthemes::import_roboto_condensed() to install Roboto Condensed and
      if Arial Narrow is not on your system, please see https://bit.ly/arialnarrow

Registered S3 method overwritten by 'enrichplot':
  method               from
  fortify.enrichResult DOSE
clusterProfiler v3.14.3  For help: https://guangchuangyu.github.io/software/clusterProfiler

If you use clusterProfiler in published research, please cite:
Guangchuang Yu, Li-Gen Wang, Yanyan Han, Qing-Yu He. clusterProfiler: an R package for comparing biological themes among gene clusters. OMICS: A Journal of Integrative Biology. 2012, 16(5):284-287.

载入程辑包:‘clusterProfiler’

The following object is masked from ‘package:DelayedArray’:

    simplify

Registering fonts with R

载入程辑包:‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:xgboost’:

    slice

The following object is masked from ‘package:IRanges’:

    slice

The following object is masked from ‘package:S4Vectors’:

    rename

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout

载入需要的程辑包:e1071

载入程辑包:‘widgetTools’

The following object is masked from ‘package:dplyr’:

    funs


载入程辑包:‘DynDoc’

The following object is masked from ‘package:DelayedArray’:

    path

The following object is masked from ‘package:BiocGenerics’:

    path


载入程辑包:‘DT’

The following object is masked from ‘package:Seurat’:

    JS

========================================
circlize version 0.4.13
CRAN page: https://cran.r-project.org/package=circlize
Github page: https://github.com/jokergoo/circlize
Documentation: https://jokergoo.github.io/circlize_book/book/

If you use it in published research, please cite:
Gu, Z. circlize implements and enhances circular visualization
  in R. Bioinformatics 2014.

This message can be suppressed by:
  suppressPackageStartupMessages(library(circlize))
========================================

载入需要的程辑包:grid
========================================
ComplexHeatmap version 2.2.0
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://jokergoo.github.io/ComplexHeatmap-reference

If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional 
  genomic data. Bioinformatics 2016.
========================================


载入程辑包:‘ComplexHeatmap’

The following object is masked from ‘package:plotly’:

    add_heatmap

构造ref ds0

ds0 <- readRDS("ds0.rds")
ref_sce <- as.SingleCellExperiment(ds0)
logcounts(ref_sce) <- log2(counts(ref_sce) + 1)

counts(ref_sce) <- as.matrix(counts(ref_sce))
# normcounts(ref_sce) <- as.matrix(normcounts(ref_sce))
logcounts(ref_sce) <- as.matrix(logcounts(ref_sce))

rowData(ref_sce)$feature_symbol <- rownames(ref_sce)
ref_sce <- ref_sce[!duplicated(rownames(ref_sce)), ]
ref_sce <- selectFeatures(ref_sce, suppress_plot = FALSE)
Warning: 'isSpike' is deprecated.
See help("Deprecated")

ref_sce <- indexCell(ref_sce)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)

环回ds0

scmapCell_results <- scmapCell(ref_sce, list(ds0 = metadata(ref_sce)$scmap_cell_index))
scmapCell_clusters <- scmapCell2Cluster(
  scmapCell_results, 
  list(as.character(colData(ref_sce)$Classification1)))

temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds0"],row.names = colnames(ds0))
Idents(ds0) <- temp
ggsave("./scmap/scmap_ds0tods0.svg", device = svg, width = 6, height = 4, plot = umapplot(ds0))

fig <- plot_ly(data.frame(table(temp)), labels = ~temp, values = ~Freq, type = 'pie',
        textposition = 'inside',
        textinfo = 'label+percent+value',
        insidetextfont = list(color = '#000000'),
        hoverinfo = 'text',
        text = ~paste0('cell numbers: ', Freq),
        marker = list(colors = colors_list,
                      line = list(color = '#FFFFFF', width = 0)),
        showlegend = FALSE) %>% layout(title = 'scmap_ds0tods0',
         xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE), 
         font = list(family = "Arial", size = 25, color = "black"))

fig

计算ARI = 0.8925608

mclust::adjustedRandIndex(temp[,1], ds0$Classification1)
[1] 0.8925608

confusing matrix

conmat <- table(as.character(ds0$Classification1), temp[,1], dnn=c("true","pre"))
conmat_prop <- prop.table(conmat, 1)
conmat_prop
              pre
true             Fibroblast Fibromyocyte     Pericyte          SMC   unassigned      Unknown
  Fibroblast   0.9745106964 0.0009103323 0.0000000000 0.0000000000 0.0241238052 0.0004551661
  Fibromyocyte 0.0086419753 0.7777777778 0.0012345679 0.0086419753 0.2037037037 0.0000000000
  Pericyte     0.0016090105 0.0048270314 0.9452936444 0.0016090105 0.0466613033 0.0000000000
  SMC          0.0000000000 0.0173010381 0.0057670127 0.8223760092 0.1545559400 0.0000000000
  Unknown      0.0833333333 0.0000000000 0.0000000000 0.0000000000 0.1715686275 0.7450980392
confuse_bubblemat(conmat_prop, rownames(conmat_prop),  colnames(conmat_prop),"ds0_scmap")

XGBoost

Idents(ds0) <- ds0$Classification1
ds0 <- RenameIdents(ds0, 'Fibroblast' = 0, 'SMC' = 1, 'Fibromyocyte' = 2, 'Pericyte' = 3, 'Unknown' = 4)
umapplot(ds0)

ds0_data <- get_data_table(ds0, highvar = F, type = "data")
ds0_label <- as.numeric(as.character(Idents(ds0)))

set.seed(7)
index <- c(1:dim(ds0_data)[2]) %>% sample(ceiling(0.3*dim(ds0_data)[2]), replace = F, prob = NULL)

colnames(ds0_data) <- NULL

ds0_train_data <- list(data = t(as(ds0_data[,-index],"dgCMatrix")), label = ds0_label[-index])
ds0_test_data <- list(data = t(as(ds0_data[,index],"dgCMatrix")), label = ds0_label[index])

ds0_train <- xgb.DMatrix(data = ds0_train_data$data,label = ds0_train_data$label)
ds0_test <- xgb.DMatrix(data = ds0_test_data$data,label = ds0_test_data$label)


watchlist <- list(train = ds0_train, eval = ds0_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds0))),
                  objective = "multi:softmax", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, ds0_train, nrounds = 100, watchlist, verbose = 0)
predict_ds0_test <- round(predict(bst_model, newdata = ds0_test))
ds0_confuse_matrix_test <- table(ds0_test_data$label, predict_ds0_test, dnn=c("true","pre"))
ds0_confuse_matrix_test_prop <- prop.table(ds0_confuse_matrix_test, 1)
ds0_confuse_matrix_test_prop
    pre
true           0           1           2           3           4
   0 0.989345510 0.000000000 0.004566210 0.000000000 0.006088280
   1 0.000000000 0.923344948 0.052264808 0.024390244 0.000000000
   2 0.025316456 0.042194093 0.919831224 0.012658228 0.000000000
   3 0.000000000 0.002849003 0.005698006 0.991452991 0.000000000
   4 0.076923077 0.000000000 0.015384615 0.000000000 0.907692308
confuse_bubblemat(ds0_confuse_matrix_test_prop, c("Fibroblast", "SMC", "Fibromyocyte", "Pericyte", "Unknown"), c("Fibroblast", "SMC", "Fibromyocyte", "Pericyte", "Unknown"),"ds0_pretrain")

adjustedRandIndex(ds0_test_data$label, predict_ds0_test) #分类器性能
[1] 0.9316151

构造query

set.seed(1)
ds2 <- readRDS("ds2.rds")
query_sce <- as.SingleCellExperiment(ds2)
logcounts(query_sce) <- log2(counts(query_sce) + 1)

counts(query_sce) <- as.matrix(counts(query_sce))
# normcounts(query_sce) <- as.matrix(normcounts(query_sce))
logcounts(query_sce) <- as.matrix(logcounts(query_sce))

rowData(query_sce)$feature_symbol <- rownames(query_sce)
query_sce <- query_sce[!duplicated(rownames(query_sce)), ]
query_sce <- selectFeatures(query_sce, suppress_plot = FALSE)
Warning: 'isSpike' is deprecated.
See help("Deprecated")

query_sce <- indexCell(query_sce)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)

环回ds2

scmapCell_results <- scmapCell(query_sce, list(ds2 = metadata(query_sce)$scmap_cell_index))
scmapCell_clusters <- scmapCell2Cluster(
  scmapCell_results, 
  list(as.character(colData(query_sce)$Classification1)))

temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds2"],row.names = colnames(ds2))
Idents(ds2) <- temp
ggsave("./scmap/scmap_ds2tods2.svg", device = svg, width = 6, height = 4, plot = umapplot(ds2))

fig <- plot_ly(data.frame(table(temp)), labels = ~temp, values = ~Freq, type = 'pie',
        textposition = 'inside',
        textinfo = 'label+percent+value',
        insidetextfont = list(color = '#000000'),
        hoverinfo = 'text',
        text = ~paste0('cell numbers: ', Freq),
        marker = list(colors = colors_list,
                      line = list(color = '#FFFFFF', width = 0)),
        showlegend = FALSE) %>% layout(title = 'scmap_ds2tods2',
         xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE), 
         font = list(family = "Arial", size = 25, color = "black"))

fig

计算ARI = 0.776329

mclust::adjustedRandIndex(temp[,1], ds2$Classification1)
[1] 0.776329

confusing matrix ds2

conmat <- table(as.character(ds2$Classification1), temp[,1], dnn=c("true","pre"))
conmat_prop <- prop.table(conmat, 1)
conmat_prop
              pre
true             Fibroblast Fibromyocyte     Pericyte         SMC1         SMC2   unassigned
  Fibroblast   0.9807846277 0.0032025620 0.0000000000 0.0000000000 0.0000000000 0.0160128102
  Fibromyocyte 0.0031222123 0.7814451383 0.0049063336 0.0107047279 0.0026761820 0.1971454059
  Pericyte     0.0000000000 0.0101073910 0.8180669615 0.0063171194 0.0000000000 0.1655085281
  SMC1         0.0000000000 0.0058292043 0.0040804430 0.8837073739 0.0040804430 0.1023025357
  SMC2         0.0009689922 0.0000000000 0.0009689922 0.0096899225 0.8226744186 0.1656976744

XGBoost ds2

Idents(ds2) <- ds2$Classification1
ds2 <- RenameIdents(ds2, 'SMC1' = 0, 'Fibromyocyte' = 1, 'Pericyte' = 2, 'Fibroblast' = 3, 'SMC2' = 4)

ds2_data <- get_data_table(ds2, highvar = F, type = "data")
ds2_label <- as.numeric(as.character(Idents(ds2)))

set.seed(7)
index <- c(1:dim(ds2_data)[2]) %>% sample(ceiling(0.3*dim(ds2_data)[2]), replace = F, prob = NULL)
colnames(ds2_data) <- NULL
ds2_train_data <- list(data = t(as(ds2_data[,-index],"dgCMatrix")), label = ds2_label[-index])
ds2_test_data <- list(data = t(as(ds2_data[,index],"dgCMatrix")), label = ds2_label[index])

ds2_train <- xgb.DMatrix(data = ds2_train_data$data,label = ds2_train_data$label)
ds2_test <- xgb.DMatrix(data = ds2_test_data$data,label = ds2_test_data$label)


watchlist <- list(train = ds2_train, eval = ds2_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2))),
                  objective = "multi:softmax", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, ds2_train, nrounds = 100, watchlist, verbose = 0)
ds2_confuse_matrix_test_prop
    pre
true           0           1           2           3           4
   0 0.964843750 0.017578125 0.011718750 0.000000000 0.005859375
   1 0.021865889 0.963556851 0.005830904 0.002915452 0.005830904
   2 0.037946429 0.033482143 0.928571429 0.000000000 0.000000000
   3 0.000000000 0.012886598 0.000000000 0.987113402 0.000000000
   4 0.012658228 0.025316456 0.000000000 0.000000000 0.962025316

ds2 project to ds0

scmapCell_results <- scmapCell(query_sce, list(ds0 = metadata(ref_sce)$scmap_cell_index))
scmapCell_clusters <- scmapCell2Cluster(
  scmapCell_results, 
  list(as.character(colData(ref_sce)$Classification1)))

temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds0"],row.names = colnames(ds2))
Idents(ds2) <- temp
# ggsave("./scmap/scmap_ds2tods0.svg", device = svg, width = 6, height = 4, plot = umapplot(ds2))

fig <- plot_ly(data.frame(table(temp)), labels = ~temp, values = ~Freq, type = 'pie',
        textposition = 'inside',
        textinfo = 'label+percent+value',
        insidetextfont = list(color = '#000000'),
        hoverinfo = 'text',
        text = ~paste0('cell numbers: ', Freq),
        marker = list(colors = colors_list,
                      line = list(color = '#FFFFFF', width = 0)),
        showlegend = FALSE) %>% layout(title = 'scmap_ds2tods0',
         xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE), 
         font = list(family = "Arial", size = 25, color = "black"))

fig
ds0FbM <- subset(ds0,ident = "Fibromyocyte")
ds2FbM <- subset(ds2,ident = "Fibromyocyte")

ds0data <- get_data_table(ds0FbM,type = "data")
ds2data <- get_data_table(ds2FbM,type = "data")

# genes_to_show <- c("IGFBP2","MGP","MYH11","DCN","TNFRSF11B")
genes_to_show <- c("DCN","LUM","MMP2","ACTA2","TNFRSF11B","FBLN1")

merge_expr <- data.frame()

for (i in lapply(genes_to_show, func1,"ds0",ds0data))
{
  merge_expr <- rbind(merge_expr,i)
}
for (i in lapply(genes_to_show, func1,"ds2",ds2data))
{
  merge_expr <- rbind(merge_expr,i)
}

rownames(merge_expr) <- NULL

Data_summary <- Rmisc::summarySE(merge_expr, measurevar="expr", groupvars=c("sample","gene"))
head(Data_summary)

ggobj <- ggplot(merge_expr,aes(x = gene, y = expr,fill = sample)) +
  geom_split_violin(trim= F, color="white", scale = "area") + 
  geom_point(data = Data_summary,aes(x = gene, y= expr), pch=19,
             position=position_dodge(0.2),size= 1) + #绘制均值位置
  geom_errorbar(data = Data_summary, aes(ymin = expr-ci, ymax= expr+ci), 
                width= 0.05, 
                position= position_dodge(0.2), #误差线位置,和均值位置相匹配
                color="black",
                alpha = 0.7,
                size= 0.5) +
  scale_fill_manual(values = c("#b1d6fb", "#fd9999"))+ 
  labs(y=("Log2 expression"),x=NULL,title = "Split violin") + 
  theme_classic()+ mytheme + stat_compare_means(aes(group = sample),
                     label = "p.format",
                     method = "wilcox.test",
                     label.y = max(merge_expr$expr),
                      hide.ns = F)
ggobj
ggsave("./scmap/scmapsupds0tods2.svg", device = svg, plot = ggobj, height = 3, width = 5)

ds0 project to ds2

构造ref

ds2 <- readRDS("ds2.rds")
ref_sce <- as.SingleCellExperiment(ds2)
logcounts(ref_sce) <- log2(counts(ref_sce) + 1)

counts(ref_sce) <- as.matrix(counts(ref_sce))
# normcounts(ref_sce) <- as.matrix(normcounts(ref_sce))
logcounts(ref_sce) <- as.matrix(logcounts(ref_sce))

rowData(ref_sce)$feature_symbol <- rownames(ref_sce)
ref_sce <- ref_sce[!duplicated(rownames(ref_sce)), ]
ref_sce <- selectFeatures(ref_sce, suppress_plot = FALSE)
ref_sce <- indexCell(ref_sce)

构造query

ds0 <- readRDS("ds0.rds")
query_sce <- as.SingleCellExperiment(ds0)
logcounts(query_sce) <- log2(counts(query_sce) + 1)

counts(query_sce) <- as.matrix(counts(query_sce))
# normcounts(query_sce) <- as.matrix(normcounts(query_sce))
logcounts(query_sce) <- as.matrix(logcounts(query_sce))

rowData(query_sce)$feature_symbol <- rownames(query_sce)
query_sce <- query_sce[!duplicated(rownames(query_sce)), ]
query_sce <- selectFeatures(query_sce, suppress_plot = FALSE)
query_sce <- indexCell(query_sce)
scmapCell_results <- scmapCell(query_sce, list(ds2 = metadata(ref_sce)$scmap_cell_index))
scmapCell_clusters <- scmapCell2Cluster(
  scmapCell_results, 
  list(as.character(colData(ref_sce)$Classification1)))
temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds2"], row.names = colnames(ds0))
Idents(ds0) <- temp
ggsave("./scmap/scmap_ds0tods2.svg", device = svg, width = 6, height = 4, plot = umapplot(ds0))

fig <- plot_ly(data.frame(table(temp)), labels = ~temp, values = ~Freq, type = 'pie',
        textposition = 'inside',
        textinfo = 'label+percent+value',
        insidetextfont = list(color = '#000000'),
        hoverinfo = 'text',
        text = ~paste0('cell numbers: ', Freq),
        marker = list(colors = colors_list,
                      line = list(color = '#FFFFFF', width = 0)),
        showlegend = FALSE) %>% layout(title = 'scmap_ds0tods2',
         xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE), 
         font = list(family = "Arial", size = 25, color = "black"))

fig
ds0FbM <- subset(ds0,ident = "Fibromyocyte")
ds2FbM <- subset(ds2,ident = "Fibromyocyte")

ds0data <- get_data_table(ds0FbM,type = "data")
ds2data <- get_data_table(ds2FbM,type = "data")

# genes_to_show <- c("IGFBP2","MGP","MYH11","DCN","TNFRSF11B")
genes_to_show <- c("DCN","LUM","MMP2","ACTA2","TNFRSF11B","FBLN1")

merge_expr <- data.frame()

for (i in lapply(genes_to_show, func1,"ds0",ds0data))
{
  merge_expr <- rbind(merge_expr,i)
}
for (i in lapply(genes_to_show, func1,"ds2",ds2data))
{
  merge_expr <- rbind(merge_expr,i)
}

rownames(merge_expr) <- NULL

Data_summary <- Rmisc::summarySE(merge_expr, measurevar="expr", groupvars=c("sample","gene"))
head(Data_summary)

ggobj <- ggplot(merge_expr,aes(x = gene, y = expr,fill = sample)) +
  geom_split_violin(trim= F, color="white", scale = "area") + 
  geom_point(data = Data_summary,aes(x = gene, y= expr), pch=19,
             position=position_dodge(0.2),size= 1) + #绘制均值位置
  geom_errorbar(data = Data_summary, aes(ymin = expr-ci, ymax= expr+ci), 
                width= 0.05, 
                position= position_dodge(0.2), #误差线位置,和均值位置相匹配
                color="black",
                alpha = 0.7,
                size= 0.5) +
  scale_fill_manual(values = c("#b1d6fb", "#fd9999"))+ 
  labs(y=("Log2 expression"),x=NULL,title = "Split violin") + 
  theme_classic()+ mytheme + stat_compare_means(aes(group = sample),
                     label = "p.format",
                     method = "wilcox.test",
                     label.y = max(merge_expr$expr),
                      hide.ns = F)
ggobj
ggsave("./scmap/scmapsupds2tods0.svg", device = svg, plot = ggobj, height = 6, width = 10)

Appendix ds1

构造ref ds1

ds1 <- readRDS("ds1.rds")
ref_sce <- as.SingleCellExperiment(ds1)
logcounts(ref_sce) <- log2(counts(ref_sce) + 1)

counts(ref_sce) <- as.matrix(counts(ref_sce))
# normcounts(ref_sce) <- as.matrix(normcounts(ref_sce))
logcounts(ref_sce) <- as.matrix(logcounts(ref_sce))

rowData(ref_sce)$feature_symbol <- rownames(ref_sce)
ref_sce <- ref_sce[!duplicated(rownames(ref_sce)), ]
ref_sce <- selectFeatures(ref_sce, suppress_plot = FALSE)
Warning: 'isSpike' is deprecated.
See help("Deprecated")

ref_sce <- indexCell(ref_sce)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)

环回ds1

scmapCell_results <- scmapCell(ref_sce, list(ds1 = metadata(ref_sce)$scmap_cell_index))
Warning: stack imbalance in '<-', 2 then 1
scmapCell_clusters <- scmapCell2Cluster(
  scmapCell_results, 
  list(as.character(colData(ref_sce)$Classification1)))

temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds1"],row.names = colnames(ds1))
Idents(ds1) <- temp
ggsave("./scmap/scmap_ds1tods1.svg", device = svg, width = 6, height = 4, plot = umapplot(ds1))

fig <- plot_ly(data.frame(table(temp)), labels = ~temp, values = ~Freq, type = 'pie',
        textposition = 'inside',
        textinfo = 'label+percent+value',
        insidetextfont = list(color = '#000000'),
        hoverinfo = 'text',
        text = ~paste0('cell numbers: ', Freq),
        marker = list(colors = colors_list,
                      line = list(color = '#FFFFFF', width = 0)),
        showlegend = FALSE) %>% layout(title = 'scmap_ds1tods1',
         xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE), 
         font = list(family = "Arial", size = 25, color = "black"))

fig

计算ARI = 0.7935582 ds1

mclust::adjustedRandIndex(temp[,1], ds1$Classification1)
[1] 0.7935582

confusing matrix

conmat <- table(as.character(ds1$Classification1), temp[,1], dnn=c("true","pre"))
conmat_prop <- prop.table(conmat, 1)
conmat_prop
              pre
true           Fibromyocyte        SMC1        SMC2  unassigned     Unknown
  Fibromyocyte  0.745973646 0.031478770 0.003660322 0.218887262 0.000000000
  SMC1          0.002773376 0.939778130 0.000792393 0.056656101 0.000000000
  SMC2          0.007518797 0.005012531 0.822055138 0.165413534 0.000000000
  Unknown       0.000000000 0.000000000 0.000000000 0.115384615 0.884615385
confuse_bubblemat(conmat_prop, rownames(conmat_prop),  colnames(conmat_prop),"ds1_scmap")

XGBoost ds1

Idents(ds1) <- ds1$Classification1
ds1 <- RenameIdents(ds1, 'Unknown' = 0, 'SMC1' = 1, 'Fibromyocyte' = 2, 'SMC2' = 3)
ds1_data <- get_data_table(ds1, highvar = F, type = "data")
ds1_label <- as.numeric(as.character(Idents(ds1)))

set.seed(7)
index <- c(1:dim(ds1_data)[2]) %>% sample(ceiling(0.3*dim(ds1_data)[2]), replace = F, prob = NULL)
colnames(ds1_data) <- NULL
ds1_train_data <- list(data = t(as(ds1_data[,-index],"dgCMatrix")), label = ds1_label[-index])
ds1_test_data <- list(data = t(as(ds1_data[,index],"dgCMatrix")), label = ds1_label[index])
ds1_train <- xgb.DMatrix(data = ds1_train_data$data,label = ds1_train_data$label)
ds1_test <- xgb.DMatrix(data = ds1_test_data$data,label = ds1_test_data$label)

watchlist <- list(train = ds1_train, eval = ds1_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds1))),
                  objective = "multi:softmax", eval_metric = 'mlogloss')
bst_model <- xgb.train(xgb_param, ds1_train, nrounds = 100, watchlist, verbose = 0)
adjustedRandIndex(ds1_test_data$label, predict_ds1_test) #ARI = 
[1] 0.8385574

XGBoost

Idents(ds0) <- ds0$Classification1
ds0 <- RenameIdents(ds0, 'Fibroblast' = 0, 'SMC' = 1, 'Fibromyocyte' = 2, 'Pericyte' = 3, 'Unknown' = 4)
ds0_data <- get_data_table(ds0, highvar = T, type = "data")
ds0_label <- as.numeric(as.character(Idents(ds0)))
ds0_ARI <- list()

for(i in seq(1:10))
{
  set.seed(17*i)
  index <- c(1:dim(ds0_data)[2]) %>% sample(ceiling(0.3*dim(ds0_data)[2]), replace = F, prob = NULL)
  colnames(ds0_data) <- NULL
  ds0_train_data <- list(data = t(as(ds0_data[,-index],"dgCMatrix")), label = ds0_label[-index])
  ds0_test_data <- list(data = t(as(ds0_data[,index],"dgCMatrix")), label = ds0_label[index])
  ds0_train <- xgb.DMatrix(data = ds0_train_data$data,label = ds0_train_data$label)
  ds0_test <- xgb.DMatrix(data = ds0_test_data$data,label = ds0_test_data$label)
  
  watchlist <- list(train = ds0_train, eval = ds0_test)
  xgb_param <- list(eta = 0.2, max_depth = 6, 
                    subsample = 0.6,  num_class = length(table(Idents(ds0))),
                    objective = "multi:softmax", eval_metric = 'mlogloss')
  bst_model <- xgb.train(xgb_param, ds0_train, nrounds = 100, watchlist, verbose = 0)
  predict_ds0_test <- round(predict(bst_model, newdata = ds0_test))
  ds0_ARI[i] <- adjustedRandIndex(ds0_test_data$label, predict_ds0_test)
}
Idents(ds1) <- ds1$Classification1
ds1 <- RenameIdents(ds1, 'Unknown' = 0, 'SMC1' = 1, 'Fibromyocyte' = 2, 'SMC2' = 3)

ds1_data <- get_data_table(ds1, highvar = T, type = "data")
ds1_label <- as.numeric(as.character(Idents(ds1)))
ds1_ARI <- list()

for(i in seq(1:10))
{
  set.seed(17*i)
  index <- c(1:dim(ds1_data)[2]) %>% sample(ceiling(0.3*dim(ds1_data)[2]), replace = F, prob = NULL)
  colnames(ds1_data) <- NULL
  ds1_train_data <- list(data = t(as(ds1_data[,-index],"dgCMatrix")), label = ds1_label[-index])
  ds1_test_data <- list(data = t(as(ds1_data[,index],"dgCMatrix")), label = ds1_label[index])
  ds1_train <- xgb.DMatrix(data = ds1_train_data$data,label = ds1_train_data$label)
  ds1_test <- xgb.DMatrix(data = ds1_test_data$data,label = ds1_test_data$label)
  
  watchlist <- list(train = ds1_train, eval = ds1_test)
  xgb_param <- list(eta = 0.2, max_depth = 6, 
                    subsample = 0.6,  num_class = length(table(Idents(ds1))),
                    objective = "multi:softmax", eval_metric = 'mlogloss')
  bst_model <- xgb.train(xgb_param, ds1_train, nrounds = 100, watchlist, verbose = 0)
  predict_ds1_test <- round(predict(bst_model, newdata = ds1_test))
  ds1_ARI[i] <- adjustedRandIndex(ds1_test_data$label, predict_ds1_test)
}
Idents(ds2) <- ds2$Classification1
ds2 <- RenameIdents(ds2, 'SMC1' = 0, 'Fibromyocyte' = 1, 'Pericyte' = 2, 'Fibroblast' = 3, 'SMC2' = 4)

ds2_data <- get_data_table(ds2, highvar = T, type = "data")
ds2_label <- as.numeric(as.character(Idents(ds2)))
ds2_ARI <- list()

for(i in seq(1:10))
{
  set.seed(17*i)
  index <- c(1:dim(ds2_data)[2]) %>% sample(ceiling(0.3*dim(ds2_data)[2]), replace = F, prob = NULL)
  colnames(ds2_data) <- NULL
  ds2_train_data <- list(data = t(as(ds2_data[,-index],"dgCMatrix")), label = ds2_label[-index])
  ds2_test_data <- list(data = t(as(ds2_data[,index],"dgCMatrix")), label = ds2_label[index])
  ds2_train <- xgb.DMatrix(data = ds2_train_data$data,label = ds2_train_data$label)
  ds2_test <- xgb.DMatrix(data = ds2_test_data$data,label = ds2_test_data$label)
  
  watchlist <- list(train = ds2_train, eval = ds2_test)
  xgb_param <- list(eta = 0.2, max_depth = 6, 
                    subsample = 0.6,  num_class = length(table(Idents(ds2))),
                    objective = "multi:softmax", eval_metric = 'mlogloss')
  bst_model <- xgb.train(xgb_param, ds2_train, nrounds = 100, watchlist, verbose = 0)
  predict_ds2_test <- round(predict(bst_model, newdata = ds2_test))
  ds2_ARI[i] <- adjustedRandIndex(ds2_test_data$label, predict_ds2_test)
}

构造ref ds0

ref_sce <- as.SingleCellExperiment(ds0)
logcounts(ref_sce) <- log2(counts(ref_sce) + 1)
counts(ref_sce) <- as.matrix(counts(ref_sce))
logcounts(ref_sce) <- as.matrix(logcounts(ref_sce))
rowData(ref_sce)$feature_symbol <- rownames(ref_sce)
ref_sce <- ref_sce[!duplicated(rownames(ref_sce)), ]
ref_sce <- selectFeatures(ref_sce, suppress_plot = FALSE)
Warning: 'isSpike' is deprecated.
See help("Deprecated")

scmapARI_ds0 <- list()

for(i in seq(1:10))
{
  set.seed(17*i)
  ref_sce <- indexCell(ref_sce)
  scmapCell_results <- scmapCell(ref_sce, list(ds0 = metadata(ref_sce)$scmap_cell_index))
  scmapCell_clusters <- scmapCell2Cluster(
    scmapCell_results, 
    list(as.character(colData(ref_sce)$Classification1)))
  temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds0"],row.names = colnames(ds0))
  scmapARI_ds0[i] <- mclust::adjustedRandIndex(temp[,1], ds0$Classification1)
}
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)

ARI ds1

ref_sce <- as.SingleCellExperiment(ds1)
logcounts(ref_sce) <- log2(counts(ref_sce) + 1)
counts(ref_sce) <- as.matrix(counts(ref_sce))
logcounts(ref_sce) <- as.matrix(logcounts(ref_sce))
rowData(ref_sce)$feature_symbol <- rownames(ref_sce)
ref_sce <- ref_sce[!duplicated(rownames(ref_sce)), ]
ref_sce <- selectFeatures(ref_sce, suppress_plot = FALSE)
Warning: 'isSpike' is deprecated.
See help("Deprecated")

scmapARI_ds1 <- list()

for(i in seq(1:10))
{
  set.seed(17*i)
  ref_sce <- indexCell(ref_sce)
  scmapCell_results <- scmapCell(ref_sce, list(ds1 = metadata(ref_sce)$scmap_cell_index))
  scmapCell_clusters <- scmapCell2Cluster(
    scmapCell_results, 
    list(as.character(colData(ref_sce)$Classification1)))
  temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds1"],row.names = colnames(ds1))
  scmapARI_ds1[i] <- mclust::adjustedRandIndex(temp[,1], ds1$Classification1)
}
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)

ARI ds2

ref_sce <- as.SingleCellExperiment(ds2)
logcounts(ref_sce) <- log2(counts(ref_sce) + 1)
counts(ref_sce) <- as.matrix(counts(ref_sce))
logcounts(ref_sce) <- as.matrix(logcounts(ref_sce))
rowData(ref_sce)$feature_symbol <- rownames(ref_sce)
ref_sce <- ref_sce[!duplicated(rownames(ref_sce)), ]
ref_sce <- selectFeatures(ref_sce, suppress_plot = FALSE)
Warning: 'isSpike' is deprecated.
See help("Deprecated")

scmapARI_ds2 <- list()

for(i in seq(1:10))
{
  set.seed(17*i)
  ref_sce <- indexCell(ref_sce)
  scmapCell_results <- scmapCell(ref_sce, list(ds2 = metadata(ref_sce)$scmap_cell_index))
  scmapCell_clusters <- scmapCell2Cluster(
    scmapCell_results, 
    list(as.character(colData(ref_sce)$Classification1)))
  temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds2"],row.names = colnames(ds2))
  scmapARI_ds2[i] <- mclust::adjustedRandIndex(temp[,1], ds2$Classification1)
}
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)

ARI performance

ARI    scmap     XGBoost
ds0   0.8925608 0.9316151
ds1   0.7935582 0.8385574
ds2   0.776329  0.9002053

##SMC2的鉴别

ds1 project to ds2

构造ref

ds2 <- readRDS("ds2.rds")
ref_sce <- as.SingleCellExperiment(ds2)
logcounts(ref_sce) <- log2(counts(ref_sce) + 1)

counts(ref_sce) <- as.matrix(counts(ref_sce))
# normcounts(ref_sce) <- as.matrix(normcounts(ref_sce))
logcounts(ref_sce) <- as.matrix(logcounts(ref_sce))

rowData(ref_sce)$feature_symbol <- rownames(ref_sce)
ref_sce <- ref_sce[!duplicated(rownames(ref_sce)), ]
ref_sce <- selectFeatures(ref_sce, suppress_plot = FALSE)
Warning: 'isSpike' is deprecated.
See help("Deprecated")

ref_sce <- indexCell(ref_sce)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)

构造query

ds1 <- readRDS("ds1.rds")
query_sce <- as.SingleCellExperiment(ds1)
logcounts(query_sce) <- log2(counts(query_sce) + 1)

counts(query_sce) <- as.matrix(counts(query_sce))
# normcounts(query_sce) <- as.matrix(normcounts(query_sce))
logcounts(query_sce) <- as.matrix(logcounts(query_sce))

rowData(query_sce)$feature_symbol <- rownames(query_sce)
query_sce <- query_sce[!duplicated(rownames(query_sce)), ]
query_sce <- selectFeatures(query_sce, suppress_plot = FALSE)
Warning: 'isSpike' is deprecated.
See help("Deprecated")

query_sce <- indexCell(query_sce)
Parameter M was not provided, will use M = n_features / 10 (if n_features <= 1000), where n_features is the number of selected features, and M = 100 otherwise.
Parameter k was not provided, will use k = sqrt(number_of_cells)
scmapCell_results <- scmapCell(query_sce, list(ds2 = metadata(ref_sce)$scmap_cell_index))
scmapCell_clusters <- scmapCell2Cluster(
  scmapCell_results, 
  list(as.character(colData(ref_sce)$Classification1)))
temp <- data.frame(scmapCell_clusters$scmap_cluster_labs[,"ds2"], row.names = colnames(ds1))
Idents(ds1) <- temp
ggsave("./scmap/scmap_ds1tods2.svg", device = svg, width = 6, height = 4, plot = umapplot(ds1))
Warning: Using `as.character()` on a quosure is deprecated as of rlang 0.3.0.
Please use `as_label()` or `as_name()` instead.
This warning is displayed once per session.
fig <- plot_ly(data.frame(table(temp)), labels = ~temp, values = ~Freq, type = 'pie',
        textposition = 'inside',
        textinfo = 'label+percent+value',
        insidetextfont = list(color = '#000000'),
        hoverinfo = 'text',
        text = ~paste0('cell numbers: ', Freq),
        marker = list(colors = colors_list,
                      line = list(color = '#FFFFFF', width = 0)),
        showlegend = FALSE) %>% layout(title = 'scmap_ds1tods2',
         xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE), 
         font = list(family = "Arial", size = 25, color = "black"))

fig

SMC2 比较 scmap vs xgboost vs unsupervised

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciouIAoKYGBge3J9CmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoc2NtYXApCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHhnYm9vc3QpCmxpYnJhcnkoTWF0cml4KQpzZXQuc2VlZCgxKQpzb3VyY2UoInRpYW5mZW5nUndyYXBwZXJzLlIiKQpgYGAKCgojIyDmnoTpgKByZWYgZHMwCmBgYHtyfQpkczAgPC0gcmVhZFJEUygiZHMwLnJkcyIpCnJlZl9zY2UgPC0gYXMuU2luZ2xlQ2VsbEV4cGVyaW1lbnQoZHMwKQpsb2djb3VudHMocmVmX3NjZSkgPC0gbG9nMihjb3VudHMocmVmX3NjZSkgKyAxKQoKY291bnRzKHJlZl9zY2UpIDwtIGFzLm1hdHJpeChjb3VudHMocmVmX3NjZSkpCiMgbm9ybWNvdW50cyhyZWZfc2NlKSA8LSBhcy5tYXRyaXgobm9ybWNvdW50cyhyZWZfc2NlKSkKbG9nY291bnRzKHJlZl9zY2UpIDwtIGFzLm1hdHJpeChsb2djb3VudHMocmVmX3NjZSkpCgpyb3dEYXRhKHJlZl9zY2UpJGZlYXR1cmVfc3ltYm9sIDwtIHJvd25hbWVzKHJlZl9zY2UpCnJlZl9zY2UgPC0gcmVmX3NjZVshZHVwbGljYXRlZChyb3duYW1lcyhyZWZfc2NlKSksIF0KcmVmX3NjZSA8LSBzZWxlY3RGZWF0dXJlcyhyZWZfc2NlLCBzdXBwcmVzc19wbG90ID0gRkFMU0UpCnJlZl9zY2UgPC0gaW5kZXhDZWxsKHJlZl9zY2UpCmBgYAojIyDnjq/lm55kczAKYGBge3J9CnNjbWFwQ2VsbF9yZXN1bHRzIDwtIHNjbWFwQ2VsbChyZWZfc2NlLCBsaXN0KGRzMCA9IG1ldGFkYXRhKHJlZl9zY2UpJHNjbWFwX2NlbGxfaW5kZXgpKQpzY21hcENlbGxfY2x1c3RlcnMgPC0gc2NtYXBDZWxsMkNsdXN0ZXIoCiAgc2NtYXBDZWxsX3Jlc3VsdHMsIAogIGxpc3QoYXMuY2hhcmFjdGVyKGNvbERhdGEocmVmX3NjZSkkQ2xhc3NpZmljYXRpb24xKSkpCgp0ZW1wIDwtIGRhdGEuZnJhbWUoc2NtYXBDZWxsX2NsdXN0ZXJzJHNjbWFwX2NsdXN0ZXJfbGFic1ssImRzMCJdLHJvdy5uYW1lcyA9IGNvbG5hbWVzKGRzMCkpCklkZW50cyhkczApIDwtIHRlbXAKZ2dzYXZlKCIuL3NjbWFwL3NjbWFwX2RzMHRvZHMwLnN2ZyIsIGRldmljZSA9IHN2Zywgd2lkdGggPSA2LCBoZWlnaHQgPSA0LCBwbG90ID0gdW1hcHBsb3QoZHMwKSkKCmZpZyA8LSBwbG90X2x5KGRhdGEuZnJhbWUodGFibGUodGVtcCkpLCBsYWJlbHMgPSB+dGVtcCwgdmFsdWVzID0gfkZyZXEsIHR5cGUgPSAncGllJywKICAgICAgICB0ZXh0cG9zaXRpb24gPSAnaW5zaWRlJywKICAgICAgICB0ZXh0aW5mbyA9ICdsYWJlbCtwZXJjZW50K3ZhbHVlJywKICAgICAgICBpbnNpZGV0ZXh0Zm9udCA9IGxpc3QoY29sb3IgPSAnIzAwMDAwMCcpLAogICAgICAgIGhvdmVyaW5mbyA9ICd0ZXh0JywKICAgICAgICB0ZXh0ID0gfnBhc3RlMCgnY2VsbCBudW1iZXJzOiAnLCBGcmVxKSwKICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9ycyA9IGNvbG9yc19saXN0LAogICAgICAgICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAnI0ZGRkZGRicsIHdpZHRoID0gMCkpLAogICAgICAgIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lIGxheW91dCh0aXRsZSA9ICdzY21hcF9kczB0b2RzMCcsCiAgICAgICAgIHhheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93dGlja2xhYmVscyA9IEZBTFNFKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHNob3dncmlkID0gRkFMU0UsIHplcm9saW5lID0gRkFMU0UsIHNob3d0aWNrbGFiZWxzID0gRkFMU0UpLCAKICAgICAgICAgZm9udCA9IGxpc3QoZmFtaWx5ID0gIkFyaWFsIiwgc2l6ZSA9IDI1LCBjb2xvciA9ICJibGFjayIpKQoKZmlnCmBgYAoKIyMg6K6h566XQVJJID0gMC44OTI1NjA4CmBgYHtyfQptY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHRlbXBbLDFdLCBkczAkQ2xhc3NpZmljYXRpb24xKQpgYGAKIyBjb25mdXNpbmcgbWF0cml4CmBgYHtyfQpjb25tYXQgPC0gdGFibGUoYXMuY2hhcmFjdGVyKGRzMCRDbGFzc2lmaWNhdGlvbjEpLCB0ZW1wWywxXSwgZG5uPWMoInRydWUiLCJwcmUiKSkKY29ubWF0X3Byb3AgPC0gcHJvcC50YWJsZShjb25tYXQsIDEpCmNvbm1hdF9wcm9wCgpjb25mdXNlX2J1YmJsZW1hdChjb25tYXRfcHJvcCwgcm93bmFtZXMoY29ubWF0X3Byb3ApLCAgY29sbmFtZXMoY29ubWF0X3Byb3ApLCJkczBfc2NtYXAiKQpgYGAKCgojIyBYR0Jvb3N0CmBgYHtyfQpJZGVudHMoZHMwKSA8LSBkczAkQ2xhc3NpZmljYXRpb24xCmRzMCA8LSBSZW5hbWVJZGVudHMoZHMwLCAnRmlicm9ibGFzdCcgPSAwLCAnU01DJyA9IDEsICdGaWJyb215b2N5dGUnID0gMiwgJ1BlcmljeXRlJyA9IDMsICdVbmtub3duJyA9IDQpCnVtYXBwbG90KGRzMCkKZHMwX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMwLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKZHMwX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhkczApKSkKCnNldC5zZWVkKDcpCmluZGV4IDwtIGMoMTpkaW0oZHMwX2RhdGEpWzJdKSAlPiUgc2FtcGxlKGNlaWxpbmcoMC4zKmRpbShkczBfZGF0YSlbMl0pLCByZXBsYWNlID0gRiwgcHJvYiA9IE5VTEwpCgpjb2xuYW1lcyhkczBfZGF0YSkgPC0gTlVMTAoKZHMwX3RyYWluX2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczBfZGF0YVssLWluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMF9sYWJlbFstaW5kZXhdKQpkczBfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMwX2RhdGFbLGluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMF9sYWJlbFtpbmRleF0pCgpkczBfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMF90cmFpbl9kYXRhJGRhdGEsbGFiZWwgPSBkczBfdHJhaW5fZGF0YSRsYWJlbCkKZHMwX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMF90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMF90ZXN0X2RhdGEkbGFiZWwpCgoKd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSBkczBfdHJhaW4sIGV2YWwgPSBkczBfdGVzdCkKeGdiX3BhcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczApKSksCiAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0bWF4IiwgZXZhbF9tZXRyaWMgPSAnbWxvZ2xvc3MnKQoKYnN0X21vZGVsIDwtIHhnYi50cmFpbih4Z2JfcGFyYW0sIGRzMF90cmFpbiwgbnJvdW5kcyA9IDEwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKCmBgYAoKYGBge3IsZmlnLmhlaWdodD00LGZpZy53aWR0aD00fQpwcmVkaWN0X2RzMF90ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMwX3Rlc3QpKQpkczBfY29uZnVzZV9tYXRyaXhfdGVzdCA8LSB0YWJsZShkczBfdGVzdF9kYXRhJGxhYmVsLCBwcmVkaWN0X2RzMF90ZXN0LCBkbm49YygidHJ1ZSIsInByZSIpKQpkczBfY29uZnVzZV9tYXRyaXhfdGVzdF9wcm9wIDwtIHByb3AudGFibGUoZHMwX2NvbmZ1c2VfbWF0cml4X3Rlc3QsIDEpCmRzMF9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AKCmNvbmZ1c2VfYnViYmxlbWF0KGRzMF9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AsIGMoIkZpYnJvYmxhc3QiLCAiU01DIiwgIkZpYnJvbXlvY3l0ZSIsICJQZXJpY3l0ZSIsICJVbmtub3duIiksIGMoIkZpYnJvYmxhc3QiLCAiU01DIiwgIkZpYnJvbXlvY3l0ZSIsICJQZXJpY3l0ZSIsICJVbmtub3duIiksImRzMF9wcmV0cmFpbiIpCgphZGp1c3RlZFJhbmRJbmRleChkczBfdGVzdF9kYXRhJGxhYmVsLCBwcmVkaWN0X2RzMF90ZXN0KSAjQVJJID0gMC45MzE2MTUxCmBgYAoKCiMjIOaehOmAoHF1ZXJ5CmBgYHtyfQpzZXQuc2VlZCgxKQpkczIgPC0gcmVhZFJEUygiZHMyLnJkcyIpCnF1ZXJ5X3NjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChkczIpCmxvZ2NvdW50cyhxdWVyeV9zY2UpIDwtIGxvZzIoY291bnRzKHF1ZXJ5X3NjZSkgKyAxKQoKY291bnRzKHF1ZXJ5X3NjZSkgPC0gYXMubWF0cml4KGNvdW50cyhxdWVyeV9zY2UpKQojIG5vcm1jb3VudHMocXVlcnlfc2NlKSA8LSBhcy5tYXRyaXgobm9ybWNvdW50cyhxdWVyeV9zY2UpKQpsb2djb3VudHMocXVlcnlfc2NlKSA8LSBhcy5tYXRyaXgobG9nY291bnRzKHF1ZXJ5X3NjZSkpCgpyb3dEYXRhKHF1ZXJ5X3NjZSkkZmVhdHVyZV9zeW1ib2wgPC0gcm93bmFtZXMocXVlcnlfc2NlKQpxdWVyeV9zY2UgPC0gcXVlcnlfc2NlWyFkdXBsaWNhdGVkKHJvd25hbWVzKHF1ZXJ5X3NjZSkpLCBdCnF1ZXJ5X3NjZSA8LSBzZWxlY3RGZWF0dXJlcyhxdWVyeV9zY2UsIHN1cHByZXNzX3Bsb3QgPSBGQUxTRSkKcXVlcnlfc2NlIDwtIGluZGV4Q2VsbChxdWVyeV9zY2UpCmBgYAoKIyMg546v5ZueZHMyCmBgYHtyfQpzY21hcENlbGxfcmVzdWx0cyA8LSBzY21hcENlbGwocXVlcnlfc2NlLCBsaXN0KGRzMiA9IG1ldGFkYXRhKHF1ZXJ5X3NjZSkkc2NtYXBfY2VsbF9pbmRleCkpCnNjbWFwQ2VsbF9jbHVzdGVycyA8LSBzY21hcENlbGwyQ2x1c3RlcigKICBzY21hcENlbGxfcmVzdWx0cywgCiAgbGlzdChhcy5jaGFyYWN0ZXIoY29sRGF0YShxdWVyeV9zY2UpJENsYXNzaWZpY2F0aW9uMSkpKQoKdGVtcCA8LSBkYXRhLmZyYW1lKHNjbWFwQ2VsbF9jbHVzdGVycyRzY21hcF9jbHVzdGVyX2xhYnNbLCJkczIiXSxyb3cubmFtZXMgPSBjb2xuYW1lcyhkczIpKQpJZGVudHMoZHMyKSA8LSB0ZW1wCmdnc2F2ZSgiLi9zY21hcC9zY21hcF9kczJ0b2RzMi5zdmciLCBkZXZpY2UgPSBzdmcsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwgcGxvdCA9IHVtYXBwbG90KGRzMikpCgpmaWcgPC0gcGxvdF9seShkYXRhLmZyYW1lKHRhYmxlKHRlbXApKSwgbGFiZWxzID0gfnRlbXAsIHZhbHVlcyA9IH5GcmVxLCB0eXBlID0gJ3BpZScsCiAgICAgICAgdGV4dHBvc2l0aW9uID0gJ2luc2lkZScsCiAgICAgICAgdGV4dGluZm8gPSAnbGFiZWwrcGVyY2VudCt2YWx1ZScsCiAgICAgICAgaW5zaWRldGV4dGZvbnQgPSBsaXN0KGNvbG9yID0gJyMwMDAwMDAnKSwKICAgICAgICBob3ZlcmluZm8gPSAndGV4dCcsCiAgICAgICAgdGV4dCA9IH5wYXN0ZTAoJ2NlbGwgbnVtYmVyczogJywgRnJlcSksCiAgICAgICAgbWFya2VyID0gbGlzdChjb2xvcnMgPSBjb2xvcnNfbGlzdCwKICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJyNGRkZGRkYnLCB3aWR0aCA9IDApKSwKICAgICAgICBzaG93bGVnZW5kID0gRkFMU0UpICU+JSBsYXlvdXQodGl0bGUgPSAnc2NtYXBfZHMydG9kczInLAogICAgICAgICB4YXhpcyA9IGxpc3Qoc2hvd2dyaWQgPSBGQUxTRSwgemVyb2xpbmUgPSBGQUxTRSwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSksCiAgICAgICAgIHlheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93dGlja2xhYmVscyA9IEZBTFNFKSwgCiAgICAgICAgIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJBcmlhbCIsIHNpemUgPSAyNSwgY29sb3IgPSAiYmxhY2siKSkKCmZpZwpgYGAKCiMjIOiuoeeul0FSSSA9IDAuNzc2MzI5CmBgYHtyfQptY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHRlbXBbLDFdLCBkczIkQ2xhc3NpZmljYXRpb24xKSAKYGBgCiMgY29uZnVzaW5nIG1hdHJpeCBkczIKYGBge3J9CmNvbm1hdCA8LSB0YWJsZShhcy5jaGFyYWN0ZXIoZHMyJENsYXNzaWZpY2F0aW9uMSksIHRlbXBbLDFdLCBkbm49YygidHJ1ZSIsInByZSIpKQpjb25tYXRfcHJvcCA8LSBwcm9wLnRhYmxlKGNvbm1hdCwgMSkKY29ubWF0X3Byb3AKCmNvbmZ1c2VfYnViYmxlbWF0KGNvbm1hdF9wcm9wLCByb3duYW1lcyhjb25tYXRfcHJvcCksICBjb2xuYW1lcyhjb25tYXRfcHJvcCksImRzMl9zY21hcCIpCmBgYAoKIyMgWEdCb29zdCBkczIKYGBge3J9CklkZW50cyhkczIpIDwtIGRzMiRDbGFzc2lmaWNhdGlvbjEKZHMyIDwtIFJlbmFtZUlkZW50cyhkczIsICdTTUMxJyA9IDAsICdGaWJyb215b2N5dGUnID0gMSwgJ1BlcmljeXRlJyA9IDIsICdGaWJyb2JsYXN0JyA9IDMsICdTTUMyJyA9IDQpCgpkczJfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczIsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczJfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMikpKSAKCnNldC5zZWVkKDcpCmluZGV4IDwtIGMoMTpkaW0oZHMyX2RhdGEpWzJdKSAlPiUgc2FtcGxlKGNlaWxpbmcoMC4zKmRpbShkczJfZGF0YSlbMl0pLCByZXBsYWNlID0gRiwgcHJvYiA9IE5VTEwpCmNvbG5hbWVzKGRzMl9kYXRhKSA8LSBOVUxMCmRzMl90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMyX2RhdGFbLC1pbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczJfbGFiZWxbLWluZGV4XSkKZHMyX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGRzMl9kYXRhWyxpbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczJfbGFiZWxbaW5kZXhdKQoKZHMyX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczJfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gZHMyX3RyYWluX2RhdGEkbGFiZWwpCmRzMl90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczJfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBkczJfdGVzdF9kYXRhJGxhYmVsKQoKCndhdGNobGlzdCA8LSBsaXN0KHRyYWluID0gZHMyX3RyYWluLCBldmFsID0gZHMyX3Rlc3QpCnhnYl9wYXJhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMoZHMyKSkpLAogICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAibXVsdGk6c29mdG1heCIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBkczJfdHJhaW4sIG5yb3VuZHMgPSAxMDAsIHdhdGNobGlzdCwgdmVyYm9zZSA9IDApCgpgYGAKCmBgYHtyLGZpZy5oZWlnaHQ9NCxmaWcud2lkdGg9NH0KcHJlZGljdF9kczJfdGVzdCA8LSByb3VuZChwcmVkaWN0KGJzdF9tb2RlbCwgbmV3ZGF0YSA9IGRzMl90ZXN0KSkKZHMyX2NvbmZ1c2VfbWF0cml4X3Rlc3QgPC0gdGFibGUoZHMyX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9kczJfdGVzdCwgZG5uPWMoInRydWUiLCJwcmUiKSkKZHMyX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCA8LSBwcm9wLnRhYmxlKGRzMl9jb25mdXNlX21hdHJpeF90ZXN0LCAxKQpkczJfY29uZnVzZV9tYXRyaXhfdGVzdF9wcm9wCgpjb25mdXNlX2J1YmJsZW1hdChkczJfY29uZnVzZV9tYXRyaXhfdGVzdF9wcm9wLCBjKCJTTUMxIiwgIkZpYnJvbXlvY3l0ZSIsICJQZXJpY3l0ZSIsICJGaWJyb2JsYXN0IiwgIlNNQzIiKSwgIGMoIlNNQzEiLCAiRmlicm9teW9jeXRlIiwgIlBlcmljeXRlIiwgIkZpYnJvYmxhc3QiLCAiU01DMiIpLCJkczJfcHJldHJhaW4iKQoKYWRqdXN0ZWRSYW5kSW5kZXgoZHMyX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9kczJfdGVzdCkgI0FSSSA9IDAuOTAwMjA1MwpzZXQuc2VlZCgxKQpgYGAKCiMjIGRzMiBwcm9qZWN0IHRvIGRzMApgYGB7cn0Kc2NtYXBDZWxsX3Jlc3VsdHMgPC0gc2NtYXBDZWxsKHF1ZXJ5X3NjZSwgbGlzdChkczAgPSBtZXRhZGF0YShyZWZfc2NlKSRzY21hcF9jZWxsX2luZGV4KSkKc2NtYXBDZWxsX2NsdXN0ZXJzIDwtIHNjbWFwQ2VsbDJDbHVzdGVyKAogIHNjbWFwQ2VsbF9yZXN1bHRzLCAKICBsaXN0KGFzLmNoYXJhY3Rlcihjb2xEYXRhKHJlZl9zY2UpJENsYXNzaWZpY2F0aW9uMSkpKQoKdGVtcCA8LSBkYXRhLmZyYW1lKHNjbWFwQ2VsbF9jbHVzdGVycyRzY21hcF9jbHVzdGVyX2xhYnNbLCJkczAiXSxyb3cubmFtZXMgPSBjb2xuYW1lcyhkczIpKQpJZGVudHMoZHMyKSA8LSB0ZW1wCiMgZ2dzYXZlKCIuL3NjbWFwL3NjbWFwX2RzMnRvZHMwLnN2ZyIsIGRldmljZSA9IHN2Zywgd2lkdGggPSA2LCBoZWlnaHQgPSA0LCBwbG90ID0gdW1hcHBsb3QoZHMyKSkKCmZpZyA8LSBwbG90X2x5KGRhdGEuZnJhbWUodGFibGUodGVtcCkpLCBsYWJlbHMgPSB+dGVtcCwgdmFsdWVzID0gfkZyZXEsIHR5cGUgPSAncGllJywKICAgICAgICB0ZXh0cG9zaXRpb24gPSAnaW5zaWRlJywKICAgICAgICB0ZXh0aW5mbyA9ICdsYWJlbCtwZXJjZW50K3ZhbHVlJywKICAgICAgICBpbnNpZGV0ZXh0Zm9udCA9IGxpc3QoY29sb3IgPSAnIzAwMDAwMCcpLAogICAgICAgIGhvdmVyaW5mbyA9ICd0ZXh0JywKICAgICAgICB0ZXh0ID0gfnBhc3RlMCgnY2VsbCBudW1iZXJzOiAnLCBGcmVxKSwKICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9ycyA9IGNvbG9yc19saXN0LAogICAgICAgICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAnI0ZGRkZGRicsIHdpZHRoID0gMCkpLAogICAgICAgIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lIGxheW91dCh0aXRsZSA9ICdzY21hcF9kczJ0b2RzMCcsCiAgICAgICAgIHhheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93dGlja2xhYmVscyA9IEZBTFNFKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHNob3dncmlkID0gRkFMU0UsIHplcm9saW5lID0gRkFMU0UsIHNob3d0aWNrbGFiZWxzID0gRkFMU0UpLCAKICAgICAgICAgZm9udCA9IGxpc3QoZmFtaWx5ID0gIkFyaWFsIiwgc2l6ZSA9IDI1LCBjb2xvciA9ICJibGFjayIpKQoKZmlnCmBgYAoKYGBge3J9CmRzMEZiTSA8LSBzdWJzZXQoZHMwLGlkZW50ID0gIkZpYnJvbXlvY3l0ZSIpCmRzMkZiTSA8LSBzdWJzZXQoZHMyLGlkZW50ID0gIkZpYnJvbXlvY3l0ZSIpCgpkczBkYXRhIDwtIGdldF9kYXRhX3RhYmxlKGRzMEZiTSx0eXBlID0gImRhdGEiKQpkczJkYXRhIDwtIGdldF9kYXRhX3RhYmxlKGRzMkZiTSx0eXBlID0gImRhdGEiKQoKIyBnZW5lc190b19zaG93IDwtIGMoIklHRkJQMiIsIk1HUCIsIk1ZSDExIiwiRENOIiwiVE5GUlNGMTFCIikKZ2VuZXNfdG9fc2hvdyA8LSBjKCJEQ04iLCJMVU0iLCJNTVAyIiwiQUNUQTIiLCJUTkZSU0YxMUIiLCJGQkxOMSIpCgptZXJnZV9leHByIDwtIGRhdGEuZnJhbWUoKQoKZm9yIChpIGluIGxhcHBseShnZW5lc190b19zaG93LCBmdW5jMSwiZHMwIixkczBkYXRhKSkKewogIG1lcmdlX2V4cHIgPC0gcmJpbmQobWVyZ2VfZXhwcixpKQp9CmZvciAoaSBpbiBsYXBwbHkoZ2VuZXNfdG9fc2hvdywgZnVuYzEsImRzMiIsZHMyZGF0YSkpCnsKICBtZXJnZV9leHByIDwtIHJiaW5kKG1lcmdlX2V4cHIsaSkKfQoKcm93bmFtZXMobWVyZ2VfZXhwcikgPC0gTlVMTAoKRGF0YV9zdW1tYXJ5IDwtIFJtaXNjOjpzdW1tYXJ5U0UobWVyZ2VfZXhwciwgbWVhc3VyZXZhcj0iZXhwciIsIGdyb3VwdmFycz1jKCJzYW1wbGUiLCJnZW5lIikpCmhlYWQoRGF0YV9zdW1tYXJ5KQoKZ2dvYmogPC0gZ2dwbG90KG1lcmdlX2V4cHIsYWVzKHggPSBnZW5lLCB5ID0gZXhwcixmaWxsID0gc2FtcGxlKSkgKwogIGdlb21fc3BsaXRfdmlvbGluKHRyaW09IEYsIGNvbG9yPSJ3aGl0ZSIsIHNjYWxlID0gImFyZWEiKSArIAogIGdlb21fcG9pbnQoZGF0YSA9IERhdGFfc3VtbWFyeSxhZXMoeCA9IGdlbmUsIHk9IGV4cHIpLCBwY2g9MTksCiAgICAgICAgICAgICBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSgwLjIpLHNpemU9IDEpICsgI+e7mOWItuWdh+WAvOS9jee9rgogIGdlb21fZXJyb3JiYXIoZGF0YSA9IERhdGFfc3VtbWFyeSwgYWVzKHltaW4gPSBleHByLWNpLCB5bWF4PSBleHByK2NpKSwgCiAgICAgICAgICAgICAgICB3aWR0aD0gMC4wNSwgCiAgICAgICAgICAgICAgICBwb3NpdGlvbj0gcG9zaXRpb25fZG9kZ2UoMC4yKSwgI+ivr+W3rue6v+S9jee9ru+8jOWSjOWdh+WAvOS9jee9ruebuOWMuemFjQogICAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIiwKICAgICAgICAgICAgICAgIGFscGhhID0gMC43LAogICAgICAgICAgICAgICAgc2l6ZT0gMC41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2IxZDZmYiIsICIjZmQ5OTk5IikpKyAKICBsYWJzKHk9KCJMb2cyIGV4cHJlc3Npb24iKSx4PU5VTEwsdGl0bGUgPSAiU3BsaXQgdmlvbGluIikgKyAKICB0aGVtZV9jbGFzc2ljKCkrIG15dGhlbWUgKyBzdGF0X2NvbXBhcmVfbWVhbnMoYWVzKGdyb3VwID0gc2FtcGxlKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSAicC5mb3JtYXQiLAogICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAid2lsY294LnRlc3QiLAogICAgICAgICAgICAgICAgICAgICBsYWJlbC55ID0gbWF4KG1lcmdlX2V4cHIkZXhwciksCiAgICAgICAgICAgICAgICAgICAgICBoaWRlLm5zID0gRikKZ2dvYmoKZ2dzYXZlKCIuL3NjbWFwL3NjbWFwc3VwZHMwdG9kczIuc3ZnIiwgZGV2aWNlID0gc3ZnLCBwbG90ID0gZ2dvYmosIGhlaWdodCA9IDMsIHdpZHRoID0gNSkKYGBgCgojIGRzMCBwcm9qZWN0IHRvIGRzMgojIyDmnoTpgKByZWYKYGBge3J9CmRzMiA8LSByZWFkUkRTKCJkczIucmRzIikKcmVmX3NjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChkczIpCmxvZ2NvdW50cyhyZWZfc2NlKSA8LSBsb2cyKGNvdW50cyhyZWZfc2NlKSArIDEpCgpjb3VudHMocmVmX3NjZSkgPC0gYXMubWF0cml4KGNvdW50cyhyZWZfc2NlKSkKIyBub3JtY291bnRzKHJlZl9zY2UpIDwtIGFzLm1hdHJpeChub3JtY291bnRzKHJlZl9zY2UpKQpsb2djb3VudHMocmVmX3NjZSkgPC0gYXMubWF0cml4KGxvZ2NvdW50cyhyZWZfc2NlKSkKCnJvd0RhdGEocmVmX3NjZSkkZmVhdHVyZV9zeW1ib2wgPC0gcm93bmFtZXMocmVmX3NjZSkKcmVmX3NjZSA8LSByZWZfc2NlWyFkdXBsaWNhdGVkKHJvd25hbWVzKHJlZl9zY2UpKSwgXQpyZWZfc2NlIDwtIHNlbGVjdEZlYXR1cmVzKHJlZl9zY2UsIHN1cHByZXNzX3Bsb3QgPSBGQUxTRSkKcmVmX3NjZSA8LSBpbmRleENlbGwocmVmX3NjZSkKYGBgCgojIyDmnoTpgKBxdWVyeQpgYGB7cn0KZHMwIDwtIHJlYWRSRFMoImRzMC5yZHMiKQpxdWVyeV9zY2UgPC0gYXMuU2luZ2xlQ2VsbEV4cGVyaW1lbnQoZHMwKQpsb2djb3VudHMocXVlcnlfc2NlKSA8LSBsb2cyKGNvdW50cyhxdWVyeV9zY2UpICsgMSkKCmNvdW50cyhxdWVyeV9zY2UpIDwtIGFzLm1hdHJpeChjb3VudHMocXVlcnlfc2NlKSkKIyBub3JtY291bnRzKHF1ZXJ5X3NjZSkgPC0gYXMubWF0cml4KG5vcm1jb3VudHMocXVlcnlfc2NlKSkKbG9nY291bnRzKHF1ZXJ5X3NjZSkgPC0gYXMubWF0cml4KGxvZ2NvdW50cyhxdWVyeV9zY2UpKQoKcm93RGF0YShxdWVyeV9zY2UpJGZlYXR1cmVfc3ltYm9sIDwtIHJvd25hbWVzKHF1ZXJ5X3NjZSkKcXVlcnlfc2NlIDwtIHF1ZXJ5X3NjZVshZHVwbGljYXRlZChyb3duYW1lcyhxdWVyeV9zY2UpKSwgXQpxdWVyeV9zY2UgPC0gc2VsZWN0RmVhdHVyZXMocXVlcnlfc2NlLCBzdXBwcmVzc19wbG90ID0gRkFMU0UpCnF1ZXJ5X3NjZSA8LSBpbmRleENlbGwocXVlcnlfc2NlKQpgYGAKCmBgYHtyfQpzY21hcENlbGxfcmVzdWx0cyA8LSBzY21hcENlbGwocXVlcnlfc2NlLCBsaXN0KGRzMiA9IG1ldGFkYXRhKHJlZl9zY2UpJHNjbWFwX2NlbGxfaW5kZXgpKQpzY21hcENlbGxfY2x1c3RlcnMgPC0gc2NtYXBDZWxsMkNsdXN0ZXIoCiAgc2NtYXBDZWxsX3Jlc3VsdHMsIAogIGxpc3QoYXMuY2hhcmFjdGVyKGNvbERhdGEocmVmX3NjZSkkQ2xhc3NpZmljYXRpb24xKSkpCnRlbXAgPC0gZGF0YS5mcmFtZShzY21hcENlbGxfY2x1c3RlcnMkc2NtYXBfY2x1c3Rlcl9sYWJzWywiZHMyIl0sIHJvdy5uYW1lcyA9IGNvbG5hbWVzKGRzMCkpCklkZW50cyhkczApIDwtIHRlbXAKZ2dzYXZlKCIuL3NjbWFwL3NjbWFwX2RzMHRvZHMyLnN2ZyIsIGRldmljZSA9IHN2Zywgd2lkdGggPSA2LCBoZWlnaHQgPSA0LCBwbG90ID0gdW1hcHBsb3QoZHMwKSkKCmZpZyA8LSBwbG90X2x5KGRhdGEuZnJhbWUodGFibGUodGVtcCkpLCBsYWJlbHMgPSB+dGVtcCwgdmFsdWVzID0gfkZyZXEsIHR5cGUgPSAncGllJywKICAgICAgICB0ZXh0cG9zaXRpb24gPSAnaW5zaWRlJywKICAgICAgICB0ZXh0aW5mbyA9ICdsYWJlbCtwZXJjZW50K3ZhbHVlJywKICAgICAgICBpbnNpZGV0ZXh0Zm9udCA9IGxpc3QoY29sb3IgPSAnIzAwMDAwMCcpLAogICAgICAgIGhvdmVyaW5mbyA9ICd0ZXh0JywKICAgICAgICB0ZXh0ID0gfnBhc3RlMCgnY2VsbCBudW1iZXJzOiAnLCBGcmVxKSwKICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9ycyA9IGNvbG9yc19saXN0LAogICAgICAgICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAnI0ZGRkZGRicsIHdpZHRoID0gMCkpLAogICAgICAgIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lIGxheW91dCh0aXRsZSA9ICdzY21hcF9kczB0b2RzMicsCiAgICAgICAgIHhheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93dGlja2xhYmVscyA9IEZBTFNFKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHNob3dncmlkID0gRkFMU0UsIHplcm9saW5lID0gRkFMU0UsIHNob3d0aWNrbGFiZWxzID0gRkFMU0UpLCAKICAgICAgICAgZm9udCA9IGxpc3QoZmFtaWx5ID0gIkFyaWFsIiwgc2l6ZSA9IDI1LCBjb2xvciA9ICJibGFjayIpKQoKZmlnCmBgYAoKCgpgYGB7cn0KZHMwRmJNIDwtIHN1YnNldChkczAsaWRlbnQgPSAiRmlicm9teW9jeXRlIikKZHMyRmJNIDwtIHN1YnNldChkczIsaWRlbnQgPSAiRmlicm9teW9jeXRlIikKCmRzMGRhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMwRmJNLHR5cGUgPSAiZGF0YSIpCmRzMmRhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyRmJNLHR5cGUgPSAiZGF0YSIpCgojIGdlbmVzX3RvX3Nob3cgPC0gYygiSUdGQlAyIiwiTUdQIiwiTVlIMTEiLCJEQ04iLCJUTkZSU0YxMUIiKQpnZW5lc190b19zaG93IDwtIGMoIkRDTiIsIkxVTSIsIk1NUDIiLCJBQ1RBMiIsIlRORlJTRjExQiIsIkZCTE4xIikKCm1lcmdlX2V4cHIgPC0gZGF0YS5mcmFtZSgpCgpmb3IgKGkgaW4gbGFwcGx5KGdlbmVzX3RvX3Nob3csIGZ1bmMxLCJkczAiLGRzMGRhdGEpKQp7CiAgbWVyZ2VfZXhwciA8LSByYmluZChtZXJnZV9leHByLGkpCn0KZm9yIChpIGluIGxhcHBseShnZW5lc190b19zaG93LCBmdW5jMSwiZHMyIixkczJkYXRhKSkKewogIG1lcmdlX2V4cHIgPC0gcmJpbmQobWVyZ2VfZXhwcixpKQp9Cgpyb3duYW1lcyhtZXJnZV9leHByKSA8LSBOVUxMCgpEYXRhX3N1bW1hcnkgPC0gUm1pc2M6OnN1bW1hcnlTRShtZXJnZV9leHByLCBtZWFzdXJldmFyPSJleHByIiwgZ3JvdXB2YXJzPWMoInNhbXBsZSIsImdlbmUiKSkKaGVhZChEYXRhX3N1bW1hcnkpCgpnZ29iaiA8LSBnZ3Bsb3QobWVyZ2VfZXhwcixhZXMoeCA9IGdlbmUsIHkgPSBleHByLGZpbGwgPSBzYW1wbGUpKSArCiAgZ2VvbV9zcGxpdF92aW9saW4odHJpbT0gRiwgY29sb3I9IndoaXRlIiwgc2NhbGUgPSAiYXJlYSIpICsgCiAgZ2VvbV9wb2ludChkYXRhID0gRGF0YV9zdW1tYXJ5LGFlcyh4ID0gZ2VuZSwgeT0gZXhwciksIHBjaD0xOSwKICAgICAgICAgICAgIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKDAuMiksc2l6ZT0gMSkgKyAj57uY5Yi25Z2H5YC85L2N572uCiAgZ2VvbV9lcnJvcmJhcihkYXRhID0gRGF0YV9zdW1tYXJ5LCBhZXMoeW1pbiA9IGV4cHItY2ksIHltYXg9IGV4cHIrY2kpLCAKICAgICAgICAgICAgICAgIHdpZHRoPSAwLjA1LCAKICAgICAgICAgICAgICAgIHBvc2l0aW9uPSBwb3NpdGlvbl9kb2RnZSgwLjIpLCAj6K+v5beu57q/5L2N572u77yM5ZKM5Z2H5YC85L2N572u55u45Yy56YWNCiAgICAgICAgICAgICAgICBjb2xvcj0iYmxhY2siLAogICAgICAgICAgICAgICAgYWxwaGEgPSAwLjcsCiAgICAgICAgICAgICAgICBzaXplPSAwLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjFkNmZiIiwgIiNmZDk5OTkiKSkrIAogIGxhYnMoeT0oIkxvZzIgZXhwcmVzc2lvbiIpLHg9TlVMTCx0aXRsZSA9ICJTcGxpdCB2aW9saW4iKSArIAogIHRoZW1lX2NsYXNzaWMoKSsgbXl0aGVtZSArIHN0YXRfY29tcGFyZV9tZWFucyhhZXMoZ3JvdXAgPSBzYW1wbGUpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbCA9ICJwLmZvcm1hdCIsCiAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJ3aWxjb3gudGVzdCIsCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsLnkgPSBtYXgobWVyZ2VfZXhwciRleHByKSwKICAgICAgICAgICAgICAgICAgICAgIGhpZGUubnMgPSBGKQpnZ29iagpnZ3NhdmUoIi4vc2NtYXAvc2NtYXBzdXBkczJ0b2RzMC5zdmciLCBkZXZpY2UgPSBzdmcsIHBsb3QgPSBnZ29iaiwgaGVpZ2h0ID0gNiwgd2lkdGggPSAxMCkKYGBgCgoKCiMjIEFwcGVuZGl4IGRzMQoKIyMg5p6E6YCgcmVmIGRzMQpgYGB7cn0KZHMxIDwtIHJlYWRSRFMoImRzMS5yZHMiKQpyZWZfc2NlIDwtIGFzLlNpbmdsZUNlbGxFeHBlcmltZW50KGRzMSkKbG9nY291bnRzKHJlZl9zY2UpIDwtIGxvZzIoY291bnRzKHJlZl9zY2UpICsgMSkKCmNvdW50cyhyZWZfc2NlKSA8LSBhcy5tYXRyaXgoY291bnRzKHJlZl9zY2UpKQojIG5vcm1jb3VudHMocmVmX3NjZSkgPC0gYXMubWF0cml4KG5vcm1jb3VudHMocmVmX3NjZSkpCmxvZ2NvdW50cyhyZWZfc2NlKSA8LSBhcy5tYXRyaXgobG9nY291bnRzKHJlZl9zY2UpKQoKcm93RGF0YShyZWZfc2NlKSRmZWF0dXJlX3N5bWJvbCA8LSByb3duYW1lcyhyZWZfc2NlKQpyZWZfc2NlIDwtIHJlZl9zY2VbIWR1cGxpY2F0ZWQocm93bmFtZXMocmVmX3NjZSkpLCBdCnJlZl9zY2UgPC0gc2VsZWN0RmVhdHVyZXMocmVmX3NjZSwgc3VwcHJlc3NfcGxvdCA9IEZBTFNFKQpyZWZfc2NlIDwtIGluZGV4Q2VsbChyZWZfc2NlKQpgYGAKIyMg546v5ZueZHMxCmBgYHtyfQpzY21hcENlbGxfcmVzdWx0cyA8LSBzY21hcENlbGwocmVmX3NjZSwgbGlzdChkczEgPSBtZXRhZGF0YShyZWZfc2NlKSRzY21hcF9jZWxsX2luZGV4KSkKc2NtYXBDZWxsX2NsdXN0ZXJzIDwtIHNjbWFwQ2VsbDJDbHVzdGVyKAogIHNjbWFwQ2VsbF9yZXN1bHRzLCAKICBsaXN0KGFzLmNoYXJhY3Rlcihjb2xEYXRhKHJlZl9zY2UpJENsYXNzaWZpY2F0aW9uMSkpKQoKdGVtcCA8LSBkYXRhLmZyYW1lKHNjbWFwQ2VsbF9jbHVzdGVycyRzY21hcF9jbHVzdGVyX2xhYnNbLCJkczEiXSxyb3cubmFtZXMgPSBjb2xuYW1lcyhkczEpKQpJZGVudHMoZHMxKSA8LSB0ZW1wCmdnc2F2ZSgiLi9zY21hcC9zY21hcF9kczF0b2RzMS5zdmciLCBkZXZpY2UgPSBzdmcsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwgcGxvdCA9IHVtYXBwbG90KGRzMSkpCgpmaWcgPC0gcGxvdF9seShkYXRhLmZyYW1lKHRhYmxlKHRlbXApKSwgbGFiZWxzID0gfnRlbXAsIHZhbHVlcyA9IH5GcmVxLCB0eXBlID0gJ3BpZScsCiAgICAgICAgdGV4dHBvc2l0aW9uID0gJ2luc2lkZScsCiAgICAgICAgdGV4dGluZm8gPSAnbGFiZWwrcGVyY2VudCt2YWx1ZScsCiAgICAgICAgaW5zaWRldGV4dGZvbnQgPSBsaXN0KGNvbG9yID0gJyMwMDAwMDAnKSwKICAgICAgICBob3ZlcmluZm8gPSAndGV4dCcsCiAgICAgICAgdGV4dCA9IH5wYXN0ZTAoJ2NlbGwgbnVtYmVyczogJywgRnJlcSksCiAgICAgICAgbWFya2VyID0gbGlzdChjb2xvcnMgPSBjb2xvcnNfbGlzdCwKICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJyNGRkZGRkYnLCB3aWR0aCA9IDApKSwKICAgICAgICBzaG93bGVnZW5kID0gRkFMU0UpICU+JSBsYXlvdXQodGl0bGUgPSAnc2NtYXBfZHMxdG9kczEnLAogICAgICAgICB4YXhpcyA9IGxpc3Qoc2hvd2dyaWQgPSBGQUxTRSwgemVyb2xpbmUgPSBGQUxTRSwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSksCiAgICAgICAgIHlheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93dGlja2xhYmVscyA9IEZBTFNFKSwgCiAgICAgICAgIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJBcmlhbCIsIHNpemUgPSAyNSwgY29sb3IgPSAiYmxhY2siKSkKCmZpZwpgYGAKCiMjIOiuoeeul0FSSSA9IDAuNzkzNTU4MiBkczEKYGBge3J9Cm1jbHVzdDo6YWRqdXN0ZWRSYW5kSW5kZXgodGVtcFssMV0sIGRzMSRDbGFzc2lmaWNhdGlvbjEpCmBgYAojIGNvbmZ1c2luZyBtYXRyaXgKYGBge3J9CmNvbm1hdCA8LSB0YWJsZShhcy5jaGFyYWN0ZXIoZHMxJENsYXNzaWZpY2F0aW9uMSksIHRlbXBbLDFdLCBkbm49YygidHJ1ZSIsInByZSIpKQpjb25tYXRfcHJvcCA8LSBwcm9wLnRhYmxlKGNvbm1hdCwgMSkKY29ubWF0X3Byb3AKCmNvbmZ1c2VfYnViYmxlbWF0KGNvbm1hdF9wcm9wLCByb3duYW1lcyhjb25tYXRfcHJvcCksICBjb2xuYW1lcyhjb25tYXRfcHJvcCksImRzMV9zY21hcCIpCmBgYAoKIyMgWEdCb29zdCBkczEKYGBge3J9CklkZW50cyhkczEpIDwtIGRzMSRDbGFzc2lmaWNhdGlvbjEKZHMxIDwtIFJlbmFtZUlkZW50cyhkczEsICdVbmtub3duJyA9IDAsICdTTUMxJyA9IDEsICdGaWJyb215b2N5dGUnID0gMiwgJ1NNQzInID0gMykKZHMxX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMxLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKZHMxX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhkczEpKSkKCnNldC5zZWVkKDcpCmluZGV4IDwtIGMoMTpkaW0oZHMxX2RhdGEpWzJdKSAlPiUgc2FtcGxlKGNlaWxpbmcoMC4zKmRpbShkczFfZGF0YSlbMl0pLCByZXBsYWNlID0gRiwgcHJvYiA9IE5VTEwpCmNvbG5hbWVzKGRzMV9kYXRhKSA8LSBOVUxMCmRzMV90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMxX2RhdGFbLC1pbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczFfbGFiZWxbLWluZGV4XSkKZHMxX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGRzMV9kYXRhWyxpbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczFfbGFiZWxbaW5kZXhdKQpkczFfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMV90cmFpbl9kYXRhJGRhdGEsbGFiZWwgPSBkczFfdHJhaW5fZGF0YSRsYWJlbCkKZHMxX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMV90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMV90ZXN0X2RhdGEkbGFiZWwpCgp3YXRjaGxpc3QgPC0gbGlzdCh0cmFpbiA9IGRzMV90cmFpbiwgZXZhbCA9IGRzMV90ZXN0KQp4Z2JfcGFyYW0gPC0gbGlzdChldGEgPSAwLjIsIG1heF9kZXB0aCA9IDYsIAogICAgICAgICAgICAgICAgICBzdWJzYW1wbGUgPSAwLjYsICBudW1fY2xhc3MgPSBsZW5ndGgodGFibGUoSWRlbnRzKGRzMSkpKSwKICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRtYXgiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBkczFfdHJhaW4sIG5yb3VuZHMgPSAxMDAsIHdhdGNobGlzdCwgdmVyYm9zZSA9IDApCmBgYAoKYGBge3IsZmlnLmhlaWdodD00LGZpZy53aWR0aD00fQpwcmVkaWN0X2RzMV90ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMxX3Rlc3QpKQpkczFfY29uZnVzZV9tYXRyaXhfdGVzdCA8LSB0YWJsZShkczFfdGVzdF9kYXRhJGxhYmVsLCBwcmVkaWN0X2RzMV90ZXN0LCBkbm49YygidHJ1ZSIsInByZSIpKQpkczFfY29uZnVzZV9tYXRyaXhfdGVzdF9wcm9wIDwtIHByb3AudGFibGUoZHMxX2NvbmZ1c2VfbWF0cml4X3Rlc3QsIDEpCmRzMV9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AKCmNvbmZ1c2VfYnViYmxlbWF0KGRzMV9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AsIGMoIlVua25vd24iLCAiU01DMSIsICJGaWJyb215b2N5dGUiLCAiU01DMiIpLCBjKCJVbmtub3duIiwgIlNNQzEiLCAiRmlicm9teW9jeXRlIiwgIlNNQzIiKSwiZHMxX3ByZXRyYWluIikKCmFkanVzdGVkUmFuZEluZGV4KGRzMV90ZXN0X2RhdGEkbGFiZWwsIHByZWRpY3RfZHMxX3Rlc3QpICNBUkkgPSAwLjgzODU1NzQKYGBgCgoKIyMgWEdCb29zdApgYGB7cn0KSWRlbnRzKGRzMCkgPC0gZHMwJENsYXNzaWZpY2F0aW9uMQpkczAgPC0gUmVuYW1lSWRlbnRzKGRzMCwgJ0ZpYnJvYmxhc3QnID0gMCwgJ1NNQycgPSAxLCAnRmlicm9teW9jeXRlJyA9IDIsICdQZXJpY3l0ZScgPSAzLCAnVW5rbm93bicgPSA0KQpkczBfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczAsIGhpZ2h2YXIgPSBULCB0eXBlID0gImRhdGEiKQpkczBfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMCkpKQpkczBfQVJJIDwtIGxpc3QoKQoKZm9yKGkgaW4gc2VxKDE6MTApKQp7CiAgc2V0LnNlZWQoMTcqaSkKICBpbmRleCA8LSBjKDE6ZGltKGRzMF9kYXRhKVsyXSkgJT4lIHNhbXBsZShjZWlsaW5nKDAuMypkaW0oZHMwX2RhdGEpWzJdKSwgcmVwbGFjZSA9IEYsIHByb2IgPSBOVUxMKQogIGNvbG5hbWVzKGRzMF9kYXRhKSA8LSBOVUxMCiAgZHMwX3RyYWluX2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczBfZGF0YVssLWluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMF9sYWJlbFstaW5kZXhdKQogIGRzMF90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczBfZGF0YVssaW5kZXhdLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gZHMwX2xhYmVsW2luZGV4XSkKICBkczBfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMF90cmFpbl9kYXRhJGRhdGEsbGFiZWwgPSBkczBfdHJhaW5fZGF0YSRsYWJlbCkKICBkczBfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gZHMwX3Rlc3RfZGF0YSRkYXRhLGxhYmVsID0gZHMwX3Rlc3RfZGF0YSRsYWJlbCkKICAKICB3YXRjaGxpc3QgPC0gbGlzdCh0cmFpbiA9IGRzMF90cmFpbiwgZXZhbCA9IGRzMF90ZXN0KQogIHhnYl9wYXJhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczApKSksCiAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRtYXgiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCiAgYnN0X21vZGVsIDwtIHhnYi50cmFpbih4Z2JfcGFyYW0sIGRzMF90cmFpbiwgbnJvdW5kcyA9IDEwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKICBwcmVkaWN0X2RzMF90ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMwX3Rlc3QpKQogIGRzMF9BUklbaV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgoZHMwX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9kczBfdGVzdCkKfQpgYGAKCmBgYHtyfQpJZGVudHMoZHMxKSA8LSBkczEkQ2xhc3NpZmljYXRpb24xCmRzMSA8LSBSZW5hbWVJZGVudHMoZHMxLCAnVW5rbm93bicgPSAwLCAnU01DMScgPSAxLCAnRmlicm9teW9jeXRlJyA9IDIsICdTTUMyJyA9IDMpCgpkczFfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczEsIGhpZ2h2YXIgPSBULCB0eXBlID0gImRhdGEiKQpkczFfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMSkpKQpkczFfQVJJIDwtIGxpc3QoKQoKZm9yKGkgaW4gc2VxKDE6MTApKQp7CiAgc2V0LnNlZWQoMTcqaSkKICBpbmRleCA8LSBjKDE6ZGltKGRzMV9kYXRhKVsyXSkgJT4lIHNhbXBsZShjZWlsaW5nKDAuMypkaW0oZHMxX2RhdGEpWzJdKSwgcmVwbGFjZSA9IEYsIHByb2IgPSBOVUxMKQogIGNvbG5hbWVzKGRzMV9kYXRhKSA8LSBOVUxMCiAgZHMxX3RyYWluX2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczFfZGF0YVssLWluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMV9sYWJlbFstaW5kZXhdKQogIGRzMV90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczFfZGF0YVssaW5kZXhdLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gZHMxX2xhYmVsW2luZGV4XSkKICBkczFfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMV90cmFpbl9kYXRhJGRhdGEsbGFiZWwgPSBkczFfdHJhaW5fZGF0YSRsYWJlbCkKICBkczFfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gZHMxX3Rlc3RfZGF0YSRkYXRhLGxhYmVsID0gZHMxX3Rlc3RfZGF0YSRsYWJlbCkKICAKICB3YXRjaGxpc3QgPC0gbGlzdCh0cmFpbiA9IGRzMV90cmFpbiwgZXZhbCA9IGRzMV90ZXN0KQogIHhnYl9wYXJhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczEpKSksCiAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRtYXgiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCiAgYnN0X21vZGVsIDwtIHhnYi50cmFpbih4Z2JfcGFyYW0sIGRzMV90cmFpbiwgbnJvdW5kcyA9IDEwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKICBwcmVkaWN0X2RzMV90ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMxX3Rlc3QpKQogIGRzMV9BUklbaV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgoZHMxX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9kczFfdGVzdCkKfQpgYGAKCmBgYHtyfQpJZGVudHMoZHMyKSA8LSBkczIkQ2xhc3NpZmljYXRpb24xCmRzMiA8LSBSZW5hbWVJZGVudHMoZHMyLCAnU01DMScgPSAwLCAnRmlicm9teW9jeXRlJyA9IDEsICdQZXJpY3l0ZScgPSAyLCAnRmlicm9ibGFzdCcgPSAzLCAnU01DMicgPSA0KQoKZHMyX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyLCBoaWdodmFyID0gVCwgdHlwZSA9ICJkYXRhIikKZHMyX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhkczIpKSkKZHMyX0FSSSA8LSBsaXN0KCkKCmZvcihpIGluIHNlcSgxOjEwKSkKewogIHNldC5zZWVkKDE3KmkpCiAgaW5kZXggPC0gYygxOmRpbShkczJfZGF0YSlbMl0pICU+JSBzYW1wbGUoY2VpbGluZygwLjMqZGltKGRzMl9kYXRhKVsyXSksIHJlcGxhY2UgPSBGLCBwcm9iID0gTlVMTCkKICBjb2xuYW1lcyhkczJfZGF0YSkgPC0gTlVMTAogIGRzMl90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMyX2RhdGFbLC1pbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczJfbGFiZWxbLWluZGV4XSkKICBkczJfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMyX2RhdGFbLGluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMl9sYWJlbFtpbmRleF0pCiAgZHMyX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczJfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gZHMyX3RyYWluX2RhdGEkbGFiZWwpCiAgZHMyX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMl90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMl90ZXN0X2RhdGEkbGFiZWwpCiAgCiAgd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSBkczJfdHJhaW4sIGV2YWwgPSBkczJfdGVzdCkKICB4Z2JfcGFyYW0gPC0gbGlzdChldGEgPSAwLjIsIG1heF9kZXB0aCA9IDYsIAogICAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMoZHMyKSkpLAogICAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0bWF4IiwgZXZhbF9tZXRyaWMgPSAnbWxvZ2xvc3MnKQogIGJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBkczJfdHJhaW4sIG5yb3VuZHMgPSAxMDAsIHdhdGNobGlzdCwgdmVyYm9zZSA9IDApCiAgcHJlZGljdF9kczJfdGVzdCA8LSByb3VuZChwcmVkaWN0KGJzdF9tb2RlbCwgbmV3ZGF0YSA9IGRzMl90ZXN0KSkKICBkczJfQVJJW2ldIDwtIGFkanVzdGVkUmFuZEluZGV4KGRzMl90ZXN0X2RhdGEkbGFiZWwsIHByZWRpY3RfZHMyX3Rlc3QpCn0KYGBgCgoKIyMg5p6E6YCgcmVmIGRzMApgYGB7cn0KcmVmX3NjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChkczApCmxvZ2NvdW50cyhyZWZfc2NlKSA8LSBsb2cyKGNvdW50cyhyZWZfc2NlKSArIDEpCmNvdW50cyhyZWZfc2NlKSA8LSBhcy5tYXRyaXgoY291bnRzKHJlZl9zY2UpKQpsb2djb3VudHMocmVmX3NjZSkgPC0gYXMubWF0cml4KGxvZ2NvdW50cyhyZWZfc2NlKSkKcm93RGF0YShyZWZfc2NlKSRmZWF0dXJlX3N5bWJvbCA8LSByb3duYW1lcyhyZWZfc2NlKQpyZWZfc2NlIDwtIHJlZl9zY2VbIWR1cGxpY2F0ZWQocm93bmFtZXMocmVmX3NjZSkpLCBdCnJlZl9zY2UgPC0gc2VsZWN0RmVhdHVyZXMocmVmX3NjZSwgc3VwcHJlc3NfcGxvdCA9IEZBTFNFKQoKc2NtYXBBUklfZHMwIDwtIGxpc3QoKQoKZm9yKGkgaW4gc2VxKDE6MTApKQp7CiAgc2V0LnNlZWQoMTcqaSkKICByZWZfc2NlIDwtIGluZGV4Q2VsbChyZWZfc2NlKQogIHNjbWFwQ2VsbF9yZXN1bHRzIDwtIHNjbWFwQ2VsbChyZWZfc2NlLCBsaXN0KGRzMCA9IG1ldGFkYXRhKHJlZl9zY2UpJHNjbWFwX2NlbGxfaW5kZXgpKQogIHNjbWFwQ2VsbF9jbHVzdGVycyA8LSBzY21hcENlbGwyQ2x1c3RlcigKICAgIHNjbWFwQ2VsbF9yZXN1bHRzLCAKICAgIGxpc3QoYXMuY2hhcmFjdGVyKGNvbERhdGEocmVmX3NjZSkkQ2xhc3NpZmljYXRpb24xKSkpCiAgdGVtcCA8LSBkYXRhLmZyYW1lKHNjbWFwQ2VsbF9jbHVzdGVycyRzY21hcF9jbHVzdGVyX2xhYnNbLCJkczAiXSxyb3cubmFtZXMgPSBjb2xuYW1lcyhkczApKQogIHNjbWFwQVJJX2RzMFtpXSA8LSBtY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHRlbXBbLDFdLCBkczAkQ2xhc3NpZmljYXRpb24xKQp9CmBgYAoKIyMgQVJJIGRzMQpgYGB7cn0KcmVmX3NjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChkczEpCmxvZ2NvdW50cyhyZWZfc2NlKSA8LSBsb2cyKGNvdW50cyhyZWZfc2NlKSArIDEpCmNvdW50cyhyZWZfc2NlKSA8LSBhcy5tYXRyaXgoY291bnRzKHJlZl9zY2UpKQpsb2djb3VudHMocmVmX3NjZSkgPC0gYXMubWF0cml4KGxvZ2NvdW50cyhyZWZfc2NlKSkKcm93RGF0YShyZWZfc2NlKSRmZWF0dXJlX3N5bWJvbCA8LSByb3duYW1lcyhyZWZfc2NlKQpyZWZfc2NlIDwtIHJlZl9zY2VbIWR1cGxpY2F0ZWQocm93bmFtZXMocmVmX3NjZSkpLCBdCnJlZl9zY2UgPC0gc2VsZWN0RmVhdHVyZXMocmVmX3NjZSwgc3VwcHJlc3NfcGxvdCA9IEZBTFNFKQoKc2NtYXBBUklfZHMxIDwtIGxpc3QoKQoKZm9yKGkgaW4gc2VxKDE6MTApKQp7CiAgc2V0LnNlZWQoMTcqaSkKICByZWZfc2NlIDwtIGluZGV4Q2VsbChyZWZfc2NlKQogIHNjbWFwQ2VsbF9yZXN1bHRzIDwtIHNjbWFwQ2VsbChyZWZfc2NlLCBsaXN0KGRzMSA9IG1ldGFkYXRhKHJlZl9zY2UpJHNjbWFwX2NlbGxfaW5kZXgpKQogIHNjbWFwQ2VsbF9jbHVzdGVycyA8LSBzY21hcENlbGwyQ2x1c3RlcigKICAgIHNjbWFwQ2VsbF9yZXN1bHRzLCAKICAgIGxpc3QoYXMuY2hhcmFjdGVyKGNvbERhdGEocmVmX3NjZSkkQ2xhc3NpZmljYXRpb24xKSkpCiAgdGVtcCA8LSBkYXRhLmZyYW1lKHNjbWFwQ2VsbF9jbHVzdGVycyRzY21hcF9jbHVzdGVyX2xhYnNbLCJkczEiXSxyb3cubmFtZXMgPSBjb2xuYW1lcyhkczEpKQogIHNjbWFwQVJJX2RzMVtpXSA8LSBtY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHRlbXBbLDFdLCBkczEkQ2xhc3NpZmljYXRpb24xKQp9CmBgYAoKIyMgQVJJIGRzMgpgYGB7cn0KcmVmX3NjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChkczIpCmxvZ2NvdW50cyhyZWZfc2NlKSA8LSBsb2cyKGNvdW50cyhyZWZfc2NlKSArIDEpCmNvdW50cyhyZWZfc2NlKSA8LSBhcy5tYXRyaXgoY291bnRzKHJlZl9zY2UpKQpsb2djb3VudHMocmVmX3NjZSkgPC0gYXMubWF0cml4KGxvZ2NvdW50cyhyZWZfc2NlKSkKcm93RGF0YShyZWZfc2NlKSRmZWF0dXJlX3N5bWJvbCA8LSByb3duYW1lcyhyZWZfc2NlKQpyZWZfc2NlIDwtIHJlZl9zY2VbIWR1cGxpY2F0ZWQocm93bmFtZXMocmVmX3NjZSkpLCBdCnJlZl9zY2UgPC0gc2VsZWN0RmVhdHVyZXMocmVmX3NjZSwgc3VwcHJlc3NfcGxvdCA9IEZBTFNFKQoKc2NtYXBBUklfZHMyIDwtIGxpc3QoKQoKZm9yKGkgaW4gc2VxKDE6MTApKQp7CiAgc2V0LnNlZWQoMTcqaSkKICByZWZfc2NlIDwtIGluZGV4Q2VsbChyZWZfc2NlKQogIHNjbWFwQ2VsbF9yZXN1bHRzIDwtIHNjbWFwQ2VsbChyZWZfc2NlLCBsaXN0KGRzMiA9IG1ldGFkYXRhKHJlZl9zY2UpJHNjbWFwX2NlbGxfaW5kZXgpKQogIHNjbWFwQ2VsbF9jbHVzdGVycyA8LSBzY21hcENlbGwyQ2x1c3RlcigKICAgIHNjbWFwQ2VsbF9yZXN1bHRzLCAKICAgIGxpc3QoYXMuY2hhcmFjdGVyKGNvbERhdGEocmVmX3NjZSkkQ2xhc3NpZmljYXRpb24xKSkpCiAgdGVtcCA8LSBkYXRhLmZyYW1lKHNjbWFwQ2VsbF9jbHVzdGVycyRzY21hcF9jbHVzdGVyX2xhYnNbLCJkczIiXSxyb3cubmFtZXMgPSBjb2xuYW1lcyhkczIpKQogIHNjbWFwQVJJX2RzMltpXSA8LSBtY2x1c3Q6OmFkanVzdGVkUmFuZEluZGV4KHRlbXBbLDFdLCBkczIkQ2xhc3NpZmljYXRpb24xKQp9CmBgYAoKIyBBUkkgcGVyZm9ybWFuY2UKYGBgCkFSSSAgICBzY21hcCAgICAgWEdCb29zdApkczAgICAwLjg5MjU2MDggMC45MzE2MTUxCmRzMSAgIDAuNzkzNTU4MiAwLjgzODU1NzQKZHMyICAgMC43NzYzMjkgIDAuOTAwMjA1MwoKYGBgCmBgYHtyIEFSSSBwZXJmb3JtYW5jZSwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9Nn0KZGF0YSA8LSBkYXRhLmZyYW1lKHNjbWFwQVJJID0gYXMubnVtZXJpYyhzY21hcEFSSV9kczApLCB4Z2JBUkkgPSBhcy5udW1lcmljKGRzMF9BUkkpLCBncm91cCA9ICdkczAnKSAlPiUKICByYmluZChkYXRhLmZyYW1lKHNjbWFwQVJJID0gYXMubnVtZXJpYyhzY21hcEFSSV9kczEpLCB4Z2JBUkkgPSBhcy5udW1lcmljKGRzMV9BUkkpLCBncm91cCA9ICdkczEnKSkgJT4lIAogIHJiaW5kKGRhdGEuZnJhbWUoc2NtYXBBUkkgPSBhcy5udW1lcmljKHNjbWFwQVJJX2RzMiksIHhnYkFSSSA9IGFzLm51bWVyaWMoZHMyX0FSSSksIGdyb3VwID0gJ2RzMicpKQpkYXRhIDwtIHJlc2hhcGUyOjptZWx0KGRhdGEsIHZhbHVlLm5hbWUgPSAiQVJJIikKZGF0YSA8LSBkYXRhICU+JSBkYXRhLmZyYW1lKGlkID0gYyhzZXEoMSxkaW0oZGF0YSlbMV0vMiksc2VxKDEsZGltKGRhdGEpWzFdLzIpKSkKCnNhdmVSRFMoZGF0YSwgInhnYm9vc3RBUkkucmRzIikKCnNjbWFwIDwtIGRhdGFbZGF0YSR2YXJpYWJsZT09InNjbWFwQVJJIixdJEFSSQp4Z2IgPC0gZGF0YVtkYXRhJHZhcmlhYmxlPT0ieGdiQVJJIixdJEFSSQpzY21hcF9tZWFuIDwtIGRhdGFbZGF0YSR2YXJpYWJsZT09InNjbWFwQVJJIixdJEFSSSAlPiUgbWVhbigpCnhnYl9tZWFuIDwtIGRhdGFbZGF0YSR2YXJpYWJsZT09InhnYkFSSSIsXSRBUkkgJT4lIG1lYW4oKQoKd2lsY294LnRlc3Qoc2NtYXAsIHhnYiwgcGFpcmVkID0gVFJVRSkKCgpnZ29iaiA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IEFSSSwgY29sb3I9Z3JvdXApKSArICMKICBsYWJzKHg9InR5cGUiLCB5PSJBUkkiLCB0aXRsZT0iQVJJIGNvbXBhcmUiKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBzdGF0X2JveHBsb3Qod2lkdGg9MC41LCBvdXRsaWVyLmNvbG91ciA9IE5BLCBsd2QgPSAxKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cD1pZCksIGNvbG9yPSJncmF5IiwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjIpKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IHZhcmlhYmxlLCB5ID0gQVJJLCBjb2xvcj1ncm91cCwgZ3JvdXA9aWQpLCAjIHNoYXBlID0gdmFyaWFibGUgCiAgICAgICAgICAgICBzaXplID0gNCwgCiAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuMikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhZXJvX2NvbG9yc19saXN0KSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSBOQSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArCnRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwLGNvbG9yID0gImJsYWNrIiksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCxjb2xvciA9ICJibGFjayIpLAogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gMSksCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfbGluZShzaXplID0gMSksCiAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSkKY29tcGFyZV9tZWFucyhBUkl+dmFyaWFibGUsIGRhdGE9ZGF0YSwgbWV0aG9kID0gInQudGVzdCIsIHBhaXJlZCA9IFQsIGdyb3VwLmJ5ID0gImdyb3VwIikKCgpnZ3NhdmUoIi4vc2NtYXAvc2NtYXBWU3hnYm9vc3Quc3ZnIiwgZGV2aWNlID0gc3ZnLCB3aWR0aCA9IDYsIGhlaWdodCA9IDgsIHBsb3QgPSBnZ29iaikKYGBgCgoKCiMjU01DMueahOmJtOWIqwoKIyMjIGRzMSBwcm9qZWN0IHRvIGRzMgojIyDmnoTpgKByZWYKYGBge3J9CmRzMiA8LSByZWFkUkRTKCJkczIucmRzIikKcmVmX3NjZSA8LSBhcy5TaW5nbGVDZWxsRXhwZXJpbWVudChkczIpCmxvZ2NvdW50cyhyZWZfc2NlKSA8LSBsb2cyKGNvdW50cyhyZWZfc2NlKSArIDEpCgpjb3VudHMocmVmX3NjZSkgPC0gYXMubWF0cml4KGNvdW50cyhyZWZfc2NlKSkKIyBub3JtY291bnRzKHJlZl9zY2UpIDwtIGFzLm1hdHJpeChub3JtY291bnRzKHJlZl9zY2UpKQpsb2djb3VudHMocmVmX3NjZSkgPC0gYXMubWF0cml4KGxvZ2NvdW50cyhyZWZfc2NlKSkKCnJvd0RhdGEocmVmX3NjZSkkZmVhdHVyZV9zeW1ib2wgPC0gcm93bmFtZXMocmVmX3NjZSkKcmVmX3NjZSA8LSByZWZfc2NlWyFkdXBsaWNhdGVkKHJvd25hbWVzKHJlZl9zY2UpKSwgXQpyZWZfc2NlIDwtIHNlbGVjdEZlYXR1cmVzKHJlZl9zY2UsIHN1cHByZXNzX3Bsb3QgPSBGQUxTRSkKcmVmX3NjZSA8LSBpbmRleENlbGwocmVmX3NjZSkKYGBgCgojIyDmnoTpgKBxdWVyeQpgYGB7cn0KZHMxIDwtIHJlYWRSRFMoImRzMS5yZHMiKQpxdWVyeV9zY2UgPC0gYXMuU2luZ2xlQ2VsbEV4cGVyaW1lbnQoZHMxKQpsb2djb3VudHMocXVlcnlfc2NlKSA8LSBsb2cyKGNvdW50cyhxdWVyeV9zY2UpICsgMSkKCmNvdW50cyhxdWVyeV9zY2UpIDwtIGFzLm1hdHJpeChjb3VudHMocXVlcnlfc2NlKSkKIyBub3JtY291bnRzKHF1ZXJ5X3NjZSkgPC0gYXMubWF0cml4KG5vcm1jb3VudHMocXVlcnlfc2NlKSkKbG9nY291bnRzKHF1ZXJ5X3NjZSkgPC0gYXMubWF0cml4KGxvZ2NvdW50cyhxdWVyeV9zY2UpKQoKcm93RGF0YShxdWVyeV9zY2UpJGZlYXR1cmVfc3ltYm9sIDwtIHJvd25hbWVzKHF1ZXJ5X3NjZSkKcXVlcnlfc2NlIDwtIHF1ZXJ5X3NjZVshZHVwbGljYXRlZChyb3duYW1lcyhxdWVyeV9zY2UpKSwgXQpxdWVyeV9zY2UgPC0gc2VsZWN0RmVhdHVyZXMocXVlcnlfc2NlLCBzdXBwcmVzc19wbG90ID0gRkFMU0UpCnF1ZXJ5X3NjZSA8LSBpbmRleENlbGwocXVlcnlfc2NlKQpgYGAKCgpgYGB7cn0Kc2NtYXBDZWxsX3Jlc3VsdHMgPC0gc2NtYXBDZWxsKHF1ZXJ5X3NjZSwgbGlzdChkczIgPSBtZXRhZGF0YShyZWZfc2NlKSRzY21hcF9jZWxsX2luZGV4KSkKc2NtYXBDZWxsX2NsdXN0ZXJzIDwtIHNjbWFwQ2VsbDJDbHVzdGVyKAogIHNjbWFwQ2VsbF9yZXN1bHRzLCAKICBsaXN0KGFzLmNoYXJhY3Rlcihjb2xEYXRhKHJlZl9zY2UpJENsYXNzaWZpY2F0aW9uMSkpKQp0ZW1wIDwtIGRhdGEuZnJhbWUoc2NtYXBDZWxsX2NsdXN0ZXJzJHNjbWFwX2NsdXN0ZXJfbGFic1ssImRzMiJdLCByb3cubmFtZXMgPSBjb2xuYW1lcyhkczEpKQpJZGVudHMoZHMxKSA8LSB0ZW1wCmdnc2F2ZSgiLi9zY21hcC9zY21hcF9kczF0b2RzMi5zdmciLCBkZXZpY2UgPSBzdmcsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwgcGxvdCA9IHVtYXBwbG90KGRzMSkpCgpmaWcgPC0gcGxvdF9seShkYXRhLmZyYW1lKHRhYmxlKHRlbXApKSwgbGFiZWxzID0gfnRlbXAsIHZhbHVlcyA9IH5GcmVxLCB0eXBlID0gJ3BpZScsCiAgICAgICAgdGV4dHBvc2l0aW9uID0gJ2luc2lkZScsCiAgICAgICAgdGV4dGluZm8gPSAnbGFiZWwrcGVyY2VudCt2YWx1ZScsCiAgICAgICAgaW5zaWRldGV4dGZvbnQgPSBsaXN0KGNvbG9yID0gJyMwMDAwMDAnKSwKICAgICAgICBob3ZlcmluZm8gPSAndGV4dCcsCiAgICAgICAgdGV4dCA9IH5wYXN0ZTAoJ2NlbGwgbnVtYmVyczogJywgRnJlcSksCiAgICAgICAgbWFya2VyID0gbGlzdChjb2xvcnMgPSBjb2xvcnNfbGlzdCwKICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJyNGRkZGRkYnLCB3aWR0aCA9IDApKSwKICAgICAgICBzaG93bGVnZW5kID0gRkFMU0UpICU+JSBsYXlvdXQodGl0bGUgPSAnc2NtYXBfZHMxdG9kczInLAogICAgICAgICB4YXhpcyA9IGxpc3Qoc2hvd2dyaWQgPSBGQUxTRSwgemVyb2xpbmUgPSBGQUxTRSwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSksCiAgICAgICAgIHlheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93dGlja2xhYmVscyA9IEZBTFNFKSwgCiAgICAgICAgIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJBcmlhbCIsIHNpemUgPSAyNSwgY29sb3IgPSAiYmxhY2siKSkKCmZpZwpgYGAKCgojIFNNQzIg5q+U6L6DIHNjbWFwIHZzIHhnYm9vc3QgdnMgdW5zdXBlcnZpc2VkCmBgYHtyfQojIGRzMVNNQzIgPC0gc3Vic2V0KGRzMSxpZGVudCA9ICJTTUMyIikKIyBkczFTTUMyIDwtIHN1YnNldChkczEsaWRlbnQgPSA0KSAjIG5ld19zdXBlcnZpc2VkX2NsdXN0ZXJpbmcuUm1kOiBsaW5lIDIyIC0gODYKZHMxU01DMiA8LSBzdWJzZXQoZHMxLGlkZW50ID0gIlNNQzIiKSAjcmVhZFJEUygiZHMxLnJkcyIpIAojIGRzMlNNQzIgPC0gc3Vic2V0KGRzMixpZGVudCA9ICJTTUMyIikKZHMyU01DMiA8LSBzdWJzZXQoZHMyLGlkZW50ID0gNCkKCmRzMWRhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMxU01DMix0eXBlID0gImRhdGEiKQpkczJkYXRhIDwtIGdldF9kYXRhX3RhYmxlKGRzMlNNQzIsdHlwZSA9ICJkYXRhIikKCgpnZW5lc190b19zaG93IDwtIGMoIkRMWDYiLCJETFg2LUFTMSIsIkRMWDUiLCJTT1NUIikKCm1lcmdlX2V4cHIgPC0gZGF0YS5mcmFtZSgpCgpmb3IgKGkgaW4gbGFwcGx5KGdlbmVzX3RvX3Nob3csIGZ1bmMxLCJkczEiLGRzMWRhdGEpKQp7CiAgbWVyZ2VfZXhwciA8LSByYmluZChtZXJnZV9leHByLGkpCn0KZm9yIChpIGluIGxhcHBseShnZW5lc190b19zaG93LCBmdW5jMSwiZHMyIixkczJkYXRhKSkKewogIG1lcmdlX2V4cHIgPC0gcmJpbmQobWVyZ2VfZXhwcixpKQp9Cgpyb3duYW1lcyhtZXJnZV9leHByKSA8LSBOVUxMCgpEYXRhX3N1bW1hcnkgPC0gUm1pc2M6OnN1bW1hcnlTRShtZXJnZV9leHByLCBtZWFzdXJldmFyPSJleHByIiwgZ3JvdXB2YXJzPWMoInNhbXBsZSIsImdlbmUiKSkKaGVhZChEYXRhX3N1bW1hcnkpCgpnZ29iaiA8LSBnZ3Bsb3QobWVyZ2VfZXhwcixhZXMoeCA9IGdlbmUsIHkgPSBleHByLGZpbGwgPSBzYW1wbGUpKSArCiAgZ2VvbV9zcGxpdF92aW9saW4odHJpbT0gRiwgY29sb3I9IndoaXRlIiwgc2NhbGUgPSAiYXJlYSIpICsgCiAgZ2VvbV9wb2ludChkYXRhID0gRGF0YV9zdW1tYXJ5LGFlcyh4ID0gZ2VuZSwgeT0gZXhwciksIHBjaD0xOSwKICAgICAgICAgICAgIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKDAuMiksc2l6ZT0gMSkgKyAj57uY5Yi25Z2H5YC85L2N572uCiAgZ2VvbV9lcnJvcmJhcihkYXRhID0gRGF0YV9zdW1tYXJ5LCBhZXMoeW1pbiA9IGV4cHItY2ksIHltYXg9IGV4cHIrY2kpLCAKICAgICAgICAgICAgICAgIHdpZHRoPSAwLjA1LCAKICAgICAgICAgICAgICAgIHBvc2l0aW9uPSBwb3NpdGlvbl9kb2RnZSgwLjIpLCAj6K+v5beu57q/5L2N572u77yM5ZKM5Z2H5YC85L2N572u55u45Yy56YWNCiAgICAgICAgICAgICAgICBjb2xvcj0iYmxhY2siLAogICAgICAgICAgICAgICAgYWxwaGEgPSAwLjcsCiAgICAgICAgICAgICAgICBzaXplPSAwLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjFkNmZiIiwgIiNmZDk5OTkiKSkrIAogIGxhYnMoeT0oIkxvZzIgZXhwcmVzc2lvbiIpLHg9TlVMTCx0aXRsZSA9ICJTcGxpdCB2aW9saW4iKSArIAogIHRoZW1lX2NsYXNzaWMoKSsgbXl0aGVtZSArIHN0YXRfY29tcGFyZV9tZWFucyhhZXMoZ3JvdXAgPSBzYW1wbGUpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbCA9ICJwLmZvcm1hdCIsCiAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJ3aWxjb3gudGVzdCIsCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsLnkgPSBtYXgobWVyZ2VfZXhwciRleHByKSwKICAgICAgICAgICAgICAgICAgICAgIGhpZGUubnMgPSBGKQpnZ29iagojIGdnc2F2ZSgiLi9zY21hcC9zY21hcHN1cGRzMnRvZHMxLnN2ZyIsIGRldmljZSA9IHN2ZywgcGxvdCA9IGdnb2JqLCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEwKQojIGdnc2F2ZSgiLi9TTUMyc3VwZHMydG9kczEuc3ZnIiwgZGV2aWNlID0gc3ZnLCBwbG90ID0gZ2dvYmosIGhlaWdodCA9IDYsIHdpZHRoID0gMTApCmdnc2F2ZSgiLi9TTUMydW5kczJ0b2RzMS5zdmciLCBkZXZpY2UgPSBzdmcsIHBsb3QgPSBnZ29iaiwgaGVpZ2h0ID0gNiwgd2lkdGggPSAxMCkKYGBgCgpBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSouCgpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dCB3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MgKkN0cmwrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4KClRoZSBwcmV2aWV3IHNob3dzIHlvdSBhIHJlbmRlcmVkIEhUTUwgY29weSBvZiB0aGUgY29udGVudHMgb2YgdGhlIGVkaXRvci4gQ29uc2VxdWVudGx5LCB1bmxpa2UgKktuaXQqLCAqUHJldmlldyogZG9lcyBub3QgcnVuIGFueSBSIGNvZGUgY2h1bmtzLiBJbnN0ZWFkLCB0aGUgb3V0cHV0IG9mIHRoZSBjaHVuayB3aGVuIGl0IHdhcyBsYXN0IHJ1biBpbiB0aGUgZWRpdG9yIGlzIGRpc3BsYXllZC4K